Description

This notebook takes the data generated by the script in /Ex_Quartic, which describes the newly developed adaptive sampling and classification method, and compares it existing classification algorithms.

The classification algorithms are: * Support Vector Machines: example supervised learning problem * Gaussian mixture models: example unsupervised learning problem

For ease of calculation, the iterative refinement of the GP models are not included here, nor are the calculations of the marginalized probabilities. The SVM and GMM methods are tested with (1) the data after finding the Pareto front (before the new adaptive sampling process), (2) the data after adaptive sampling refinement, and (3) the data after the Pareto front search with additional random sampling to see if the new sampling method is useful for these other processes as well.

Comparison among the supervised methods is done by looking at the error rate across the entire space. Since the test function is a simple polynomial, the solution of what is acceptable can be found explicitly. Since the GP method produces a fuzzy classifier, the error rate is the average misclassification probability. For the SVM method, the error rate is simply the number of incorrectly categorized points divided by the total number of test points.

In addition to the accuracy comparison, a comparison of the importance ranking metric developed for using GP in classification problems will be compared to Shapley values. As with the accuracy comparison, this will be tested against the data before refinement, after refinement, or after a random sample.

Clear the workspace and define the functions.

# Setup
rm(list = ls())
# Visualization
library(dplyr)
library(ggplot2)
library(patchwork)
# Parallel processing
library(parallel)
library(doParallel)
# Gaussian processes
library(GPareto)
library(DiceKriging)
library(DiceOptim)
# Optimization
library(GA)
# Gaussian Mixture Models
library(mclust)
# Support Vector Machines
library(e1071)

Relevant functions

The quartic objective functions

# Set (x1, x2) on range of [0, 5]

# Objective functions are based on the quadratic functions used previously, but with an additional local minimum
f1 = function(x1, x2){
  return(20*(x1 - 0.75)^2 + 190 + 11.58*x2^4 - 115.85*x2^3 + 383.13*x2^2 - 463.50*x2)
}
# The second objective function is also partially rotated so the local optimum is not perfectly aligned
f2 = function(x1, x2){
  # Remap both variables: rotate 30 degrees counterclockwise
  ang = -pi/24
  n1 = x1*cos(ang) - x2*sin(ang)
  n2 = x1*sin(ang) + x2*cos(ang)
  # return((x1 - 2.5)^2 + 80 + 1.778*x2^4 - 20*x2^3 + 78.573*x2^2 - 124.664*x2)
  return((n1 - 2.5)^2 + 80 + 1.778*n2^4 - 20*n2^3 + 78.573*n2^2 - 124.664*n2)
}


# Using GPareto: Need inputs and outputs as vectors/matrices not as dataframes. Coarse initial design
fun = function(x){
  x1 = x[1]; x2 = x[2]
  return(c(f1(x1, x2), f2(x1, x2)))
}

Normalization-related functions

# Normalized objective functions
n.obj = function(GPar.data, GPar.front){
  # Given dataframes that describe the entire dataset and the front, find the normalized (x,y)
  # Objective functions are named 'f1' and 'f2'
  
  # Normalize the objective outputs so that the utopia point is (0,0) and the nadir point is (1,1)
  f1.up = GPar.front$f1[which.min(GPar.front$f2)]
  f2.up = GPar.front$f2[which.min(GPar.front$f1)]
  GPar.data$f1.norm = (GPar.data$f1 - min(GPar.front$f1))/(f1.up - min(GPar.front$f1))
  GPar.data$f2.norm = (GPar.data$f2 - min(GPar.front$f2))/(f2.up - min(GPar.front$f2))

  return(GPar.data)
}

# Calculate the normalized distance
n.dist = function(f1.norm, f2.norm, GPar.front){
  # Given the normalized coordinates (f1.norm, f2.norm) and the Pareto frontier estimate,
  # find the distance along the constant f2/f1 ratio line
  
  # Determine the two points on the Pareto front that define the relevant segment
  GPar.front$theta = atan(GPar.front$f2.norm / GPar.front$f1.norm)
  if(f1.norm < 0){f1.norm = 0}
  if(f2.norm < 0){f2.norm = 0}
  ratio = atan(f2.norm/f1.norm)
  
  # Check if the angle is the same as a point on the Pareto front
  if(any(abs(ratio - GPar.front$theta) < 1e-5)){
    pos = which.min(abs(ratio - GPar.front$theta))
    Par.x = GPar.front$f1.norm[pos]
    Par.y = GPar.front$f2.norm[pos]
  } else{ # Otherwise, two points are needed for linear interpolation
    # Break the dataframe into theta above and below
    Par.above = GPar.front[GPar.front$theta - ratio > 0,]
    Par.below = GPar.front[GPar.front$theta - ratio < 0,]
    # Find the point closest to the angle
    pos.above = which.min(abs(ratio - Par.above$theta))
    pos.below = which.min(abs(ratio - Par.below$theta))
    # Linear interpolation
    ln.x = c(Par.above$f1.norm[pos.above], Par.below$f1.norm[pos.below])
    ln.y = c(Par.above$f2.norm[pos.above], Par.below$f2.norm[pos.below])
    slp = diff(ln.y)/diff(ln.x)
    # Find the point on the segment with the same angle, ie. the same ratio.
    # Solving with this constraint has analytical solution:
    Par.x = (ln.y[1] - slp*ln.x[1]) / (f2.norm/f1.norm - slp)
    Par.y = slp*(Par.x - ln.x[1]) + ln.y[1]
  }
  
  # Linear distance to the front is the difference between distances to the origin
  dist = sqrt(f1.norm^2 + f2.norm^2) - sqrt(Par.x^2 + Par.y^2)
  return(dist)
}

Gaussian process parameter tuning

fill.sample.mod = function(GPar.data, input.name, output.name){
  # Calculate the GP model to use. 
  # Using the km function, but applies checks on the system to make sure that 
  # the model uncertainty matches expectations based on GP, ie. it did not
  # fail to converge.
  
  # Based on testing, the model is bad when the 10% percentile and 90% percentile 
  # of the standard deviation are of the same order of magnitude. This is easiest
  # checked if the difference between the 10th and 90th percentile
  # is larger than the difference between the 25th and 75th.
  pt10 = 1; pt90 = 1; pt25 = 1; pt75 = 1
  while(log10(pt90/pt10) <= log10(pt75/pt25)){
    mod.out = DiceKriging::km(design = GPar.data[, input.name], response = GPar.data[, output.name], 
                 covtyp = 'gauss', # Gaussian uncertainty
                 optim.method = 'gen', # Genetic algorithm optimization
                 control = list(trace = FALSE, # Turn off tracking to simplify output
                                pop.size = 50, # Increase robustness
                                max.generations = 400), # Some convergence issues
                 nugget = 1e-6, # Avoid eigenvalues of 0
                 )
    
    # Randomly sample 200 points from the search space
    pt = 200; i = 1
    lims = range(GPar.data[,input.name[i]])
    samp = data.frame(runif(n = pt, min = lims[1], max = lims[2]))
    for(i in 2:length(input.name)){
      lims = range(GPar.data[,input.name[i]])
      samp[,i] = runif(n = pt, min = lims[1], max = lims[2])
    }
    names(samp) = input.name
    
    # Find model output to find the percentile ranks for this iteration
    res = predict(object = mod.out, newdata = samp, type = 'UK')
    pt10 = quantile(res$sd, 0.10); pt90 = quantile(res$sd, 0.90)
    pt25 = quantile(res$sd, 0.25); pt75 = quantile(res$sd, 0.75)
  }
  return(mod.out)
}

Comparison of GP-based Boundaries with Different Acceptance Criteria

Comparison of the boundaries to show how changing the acceptance criteria changes the shape of the near-Pareto set. Showcases the robustness to different selection criteria, indicating flexibility in the design objectives.

## Loading
# Load datasets for obtaining the refined probability functions
data.delta = read.csv('../Ex_Quartic/GPar_Accept_Delta1.csv')
data.cutof = read.csv('../Ex_Quartic/GPar_Accept_Threshold.csv')
data.radan = read.csv('../Ex_Quartic/GPar_Accept_Radius.csv')
# Load datasets prior to refinement
data.paret = read.csv('../Ex_Quartic/GPar_all_start.csv')
data.paret$rad = sqrt(data.paret$f1.norm^2 + data.paret$f2.norm^2)
# Compare to the estimate of the Pareto frontier
GPar.front = read.csv(file = '../Ex_Quartic/GPar_fnt_start.csv')

# Add a random samples to simulate the effect of sample size rather than adaptive sampling
# Have it stored so for consistency, but also create multiple random sample sets
# to get a sense of the variance
if(file.exists('GPar_Random.csv') == FALSE){
  nsamp = max(nrow(data.delta), nrow(data.cutof), nrow(data.radan)) - nrow(data.paret)
  nsamp = nsamp*10 # Collect more than needed for sample size variance testing
  data.rando = data.frame(x1 = runif(n = nsamp, min = 0, max = 5),
                          x2 = runif(n = nsamp, min = 0, max = 5))
  data.rando$f1 = f1(x1 = data.rando$x1, x2 = data.rando$x2)
  data.rando$f2 = f2(x1 = data.rando$x1, x2 = data.rando$x2)
  # Fill in the remaining calculations: normalized outputs, distance, theta, order
  data.rando = n.obj(GPar.data = data.rando, GPar.front = GPar.front)
  cl = makeCluster(2)
  registerDoParallel(cl)
  dist = foreach(row = 1:nrow(data.rando)) %dopar%
    n.dist(f1.norm = data.rando$f1.norm[row], f2.norm = data.rando$f2.norm[row], GPar.front = GPar.front)
  stopCluster(cl)
  data.rando$dist = unlist(dist)
  data.rando$rad = sqrt(data.rando$f1.norm^2 + data.rando$f2.norm^2)
  data.rando$theta = atan(data.rando$f2.norm/data.rando$f1.norm)*180/pi*10/9
  data.rando$order = seq(from = max(data.paret$order) + 1, to = max(data.paret$order) + nsamp, by = 1)
  data.rando = rbind(data.paret[, names(data.paret) %in% names(data.rando)], data.rando)

  # Store the random data
  write.csv(data.rando, file = 'GPar_Random.csv', row.names = F)
}
data.rando = read.csv(file = 'GPar_Random.csv')
data.rando = data.rando[1:nrow(data.delta),] # Keep same sample size for initial testing

##
# Grid of the relevant region to visualize and compare
lower = c(0, 0); upper = c(5,5); grid.sz = 100
fine.grid = expand.grid(x1 = seq(from = lower[1], to = upper[1], length.out = grid.sz), 
                        x2 = seq(from = lower[2], to = upper[2], length.out = grid.sz))
fine.grid = fine.grid[,1:2]
names(fine.grid) = c('x1', 'x2')
# Calculate the actual results
fine.grid$f1 = f1(x1 = fine.grid$x1, x2 = fine.grid$x2)
fine.grid$f2 = f2(x1 = fine.grid$x1, x2 = fine.grid$x2)
fine.grid = n.obj(GPar.data = fine.grid, GPar.front = GPar.front)
fine.grid$dist = NaN
for(row in 1:nrow(fine.grid)){
  fine.grid$dist[row] = n.dist(f1.norm = fine.grid$f1.norm[row], 
                               f2.norm = fine.grid$f2.norm[row], 
                               GPar.front = GPar.front)
}
fine.grid$ang = atan(fine.grid$f2.norm/fine.grid$f1.norm)*180/pi*10/9
fine.grid$rad = sqrt(fine.grid$f1.norm^2 + fine.grid$f2.norm^2)
##
# Normalized distance
fine.grid$delta = 0
fine.grid$delta[fine.grid$dist <= 1] = 1

mod.dist.paret = fill.sample.mod(GPar.data = data.paret, input.name = c('x1', 'x2'), output.name = 'dist')
mod.dist.adapt = fill.sample.mod(GPar.data = data.delta, input.name = c('x1', 'x2'), output.name = 'dist')
mod.dist.rando = fill.sample.mod(GPar.data = data.rando, input.name = c('x1', 'x2'), output.name = 'dist')

res = predict(object = mod.dist.paret, newdata = fine.grid[,c('x1', 'x2')], type = "UK")
fine.grid$prob.delta.paret = pnorm(q = 0, mean = res$mean - 1, sd = res$sd)
res = predict(object = mod.dist.adapt, newdata = fine.grid[,c('x1', 'x2')], type = "UK")
fine.grid$prob.delta.adapt = pnorm(q = 0, mean = res$mean - 1, sd = res$sd)
res = predict(object = mod.dist.rando, newdata = fine.grid[,c('x1', 'x2')], type = "UK")
fine.grid$prob.delta.rando = pnorm(q = 0, mean = res$mean - 1, sd = res$sd)

##
# Threshold cutoff
fine.grid$cutof = 0
fine.grid$cutof[fine.grid$f1.norm <= 1 & fine.grid$f2.norm <= 1] = 1

mod.f1.paret = fill.sample.mod(GPar.data = data.paret, input.name = c('x1', 'x2'), output.name = 'f1.norm')
mod.f2.paret = fill.sample.mod(GPar.data = data.paret, input.name = c('x1', 'x2'), output.name = 'f2.norm')
mod.f1.adapt = fill.sample.mod(GPar.data = data.cutof, input.name = c('x1', 'x2'), output.name = 'f1.norm')
mod.f2.adapt = fill.sample.mod(GPar.data = data.cutof, input.name = c('x1', 'x2'), output.name = 'f2.norm')
mod.f1.rando = fill.sample.mod(GPar.data = data.rando, input.name = c('x1', 'x2'), output.name = 'f1.norm')
mod.f2.rando = fill.sample.mod(GPar.data = data.rando, input.name = c('x1', 'x2'), output.name = 'f2.norm')

res1 = predict(object = mod.f1.paret, newdata = data.frame(x1 = fine.grid$x1, x2 = fine.grid$x2), type = "UK")
res2 = predict(object = mod.f2.paret, newdata = data.frame(x1 = fine.grid$x1, x2 = fine.grid$x2), type = "UK")
fine.grid$prob.cutof.paret = pnorm(q = 0, mean = res1$mean - 1, sd = res1$sd) * 
  pnorm(q = 0, mean = res2$mean - 1, sd = res2$sd)
res1 = predict(object = mod.f1.adapt, newdata = data.frame(x1 = fine.grid$x1, x2 = fine.grid$x2), type = "UK")
res2 = predict(object = mod.f2.adapt, newdata = data.frame(x1 = fine.grid$x1, x2 = fine.grid$x2), type = "UK")
fine.grid$prob.cutof.adapt = pnorm(q = 0, mean = res1$mean - 1, sd = res1$sd) * 
  pnorm(q = 0, mean = res2$mean - 1, sd = res2$sd)
res1 = predict(object = mod.f1.rando, newdata = data.frame(x1 = fine.grid$x1, x2 = fine.grid$x2), type = "UK")
res2 = predict(object = mod.f2.rando, newdata = data.frame(x1 = fine.grid$x1, x2 = fine.grid$x2), type = "UK")
fine.grid$prob.cutof.rando = pnorm(q = 0, mean = res1$mean - 1, sd = res1$sd) * 
  pnorm(q = 0, mean = res2$mean - 1, sd = res2$sd)

##
# Radius-angle
fine.grid$radan = 0
fine.grid$radan[fine.grid$rad <= 1 & fine.grid$ang > 20] = 1

mod.rad.paret = fill.sample.mod(GPar.data = data.paret, input.name = c('x1', 'x2'), output.name = 'rad')
mod.ang.paret = fill.sample.mod(GPar.data = data.paret, input.name = c('x1', 'x2'), output.name = 'theta')
mod.rad.adapt = fill.sample.mod(GPar.data = data.radan, input.name = c('x1', 'x2'), output.name = 'rad')
mod.ang.adapt = fill.sample.mod(GPar.data = data.radan, input.name = c('x1', 'x2'), output.name = 'theta')
mod.rad.rando = fill.sample.mod(GPar.data = data.rando, input.name = c('x1', 'x2'), output.name = 'rad')
mod.ang.rando = fill.sample.mod(GPar.data = data.rando, input.name = c('x1', 'x2'), output.name = 'theta')

res1 = predict(object = mod.rad.paret, newdata = data.frame(x1 = fine.grid$x1, x2 = fine.grid$x2), type = "UK")
res2 = predict(object = mod.ang.paret, newdata = data.frame(x1 = fine.grid$x1, x2 = fine.grid$x2), type = "UK")
fine.grid$prob.radan.paret = pnorm(q = 0, mean = res1$mean - 1, sd = res1$sd) * 
  (1 - pnorm(q = 0, mean = res2$mean - 20, sd = res2$sd))
res1 = predict(object = mod.rad.adapt, newdata = data.frame(x1 = fine.grid$x1, x2 = fine.grid$x2), type = "UK")
res2 = predict(object = mod.ang.adapt, newdata = data.frame(x1 = fine.grid$x1, x2 = fine.grid$x2), type = "UK")
fine.grid$prob.radan.adapt = pnorm(q = 0, mean = res1$mean - 1, sd = res1$sd) * 
  (1 - pnorm(q = 0, mean = res2$mean - 20, sd = res2$sd))
res1 = predict(object = mod.rad.rando, newdata = data.frame(x1 = fine.grid$x1, x2 = fine.grid$x2), type = "UK")
res2 = predict(object = mod.ang.rando, newdata = data.frame(x1 = fine.grid$x1, x2 = fine.grid$x2), type = "UK")
fine.grid$prob.radan.rando = pnorm(q = 0, mean = res1$mean - 1, sd = res1$sd) * 
  (1 - pnorm(q = 0, mean = res2$mean - 20, sd = res2$sd))

rm(res1, res2)
sep = 0
ggplot() +
  # Boundaries: +/- some separation from 0.5
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = prob.delta.paret, color = 'delta'), 
               breaks = c(sep, -sep)+0.5) +
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = prob.cutof.paret, color = 'cutof'), 
               breaks = c(sep, -sep)+0.5) +
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = prob.radan.paret, color = 'radan'), 
               breaks = c(sep, -sep)+0.5) +
  # Pareto frontier
  geom_point(data = GPar.front, mapping = aes(x = x1, y = x2), color = 'black') + 
  geom_smooth(data = GPar.front, mapping = aes(x = x1, y = x2, color = 'Pareto'), level = 0.95, method = 'loess') + 
  labs(x = expression('x'[1]), y = expression('x'[2]), color = 'Acceptance Criteria', subtitle = 'Before Refinement') +
  scale_color_manual(values = c('delta' = 'red', 'cutof' = 'skyblue2', 'radan' = 'green', 'Pareto' = 'black'),
                     labels = c('delta' = expression(delta*' < 1'), 'cutof' = expression('F'[1]^'*'*'< 1, F'[2]^'*'*'< 1'), 
                                'radan' = expression('r < 1, '*theta*' > 18'^'o'),
                                'Pareto' = expression('Pareto Front')),
                     breaks = c('delta', 'cutof', 'radan', 'Pareto')) +
  theme_classic() + theme(legend.position = c(0.85, 0.75)) + 
  scale_x_continuous(expand = c(0, 0), limits = c(0, 5)) + scale_y_continuous(expand = c(0, 0), limits = c(0, 5)) +
  guides(colour = guide_legend(override.aes = list(fill = alpha('white', 1))))


ggplot() +
  # Boundaries: +/- some separation from 0.5
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = prob.delta.adapt, color = 'delta'), 
               breaks = c(sep, -sep)+0.5) +
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = prob.cutof.adapt, color = 'cutof'), 
               breaks = c(sep, -sep)+0.5) +
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = prob.radan.adapt, color = 'radan'), 
               breaks = c(sep, -sep)+0.5) +
  # Pareto frontier
  geom_point(data = GPar.front, mapping = aes(x = x1, y = x2), color = 'black') + 
  geom_smooth(data = GPar.front, mapping = aes(x = x1, y = x2, color = 'Pareto'), level = 0.95, method = 'loess') + 
  labs(x = expression('x'[1]), y = expression('x'[2]), color = 'Acceptance Criteria', subtitle = 'After Adaptive Sampling Refinement') +
  scale_color_manual(values = c('delta' = 'red', 'cutof' = 'skyblue2', 'radan' = 'green', 'Pareto' = 'black'),
                     labels = c('delta' = expression(delta*' < 1'), 'cutof' = expression('F'[1]^'*'*'< 1, F'[2]^'*'*'< 1'), 
                                'radan' = expression('r < 1, '*theta*' > 18'^'o'),
                                'Pareto' = expression('Pareto Front')),
                     breaks = c('delta', 'cutof', 'radan', 'Pareto')) +
  theme_classic() + theme(legend.position = c(0.85, 0.75)) + 
  scale_x_continuous(expand = c(0, 0), limits = c(0, 5)) + scale_y_continuous(expand = c(0, 0), limits = c(0, 5)) +
  guides(colour = guide_legend(override.aes = list(fill = alpha('white', 1))))


ggplot() +
  # Boundaries: +/- some separation from 0.5
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = prob.delta.rando, color = 'delta'), 
               breaks = c(sep, -sep)+0.5) +
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = prob.cutof.rando, color = 'cutof'), 
               breaks = c(sep, -sep)+0.5) +
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = prob.radan.rando, color = 'radan'), 
               breaks = c(sep, -sep)+0.5) +
  # Pareto frontier
  geom_point(data = GPar.front, mapping = aes(x = x1, y = x2), color = 'black') + 
  geom_smooth(data = GPar.front, mapping = aes(x = x1, y = x2, color = 'Pareto'), level = 0.95, method = 'loess') + 
  labs(x = expression('x'[1]), y = expression('x'[2]), color = 'Acceptance Criteria', subtitle = 'After Random Sampling') +
  scale_color_manual(values = c('delta' = 'red', 'cutof' = 'skyblue2', 'radan' = 'green', 'Pareto' = 'black'),
                     labels = c('delta' = expression(delta*' < 1'), 'cutof' = expression('F'[1]^'*'*'< 1, F'[2]^'*'*'< 1'), 
                                'radan' = expression('r < 1, '*theta*' > 18'^'o'),
                                'Pareto' = expression('Pareto Front')),
                     breaks = c('delta', 'cutof', 'radan', 'Pareto')) +
  theme_classic() + theme(legend.position = c(0.85, 0.75)) + 
  scale_x_continuous(expand = c(0, 0), limits = c(0, 5)) + scale_y_continuous(expand = c(0, 0), limits = c(0, 5)) +
  guides(colour = guide_legend(override.aes = list(fill = alpha('white', 1))))

# Plotting variables
sep = 0.05; trans = 0.25; ln.sz = 1.25

## Distance
ggplot() +
  # Boundaries: +/- some separation from 0.5
  geom_contour_filled(data = fine.grid, mapping = aes(x = x1, y = x2, z = prob.delta.paret, 
               fill = 'paret'), 
               breaks = c(sep, -sep)+0.5, linetype = 2, alpha = trans) +
  geom_contour_filled(data = fine.grid, mapping = aes(x = x1, y = x2, z = prob.delta.adapt, 
               fill = 'adapt'), 
               breaks = c(sep, -sep)+0.5, linetype = 2, alpha = trans) +
  geom_contour_filled(data = fine.grid, mapping = aes(x = x1, y = x2, z = prob.delta.rando, 
               fill = 'rando'), 
               breaks = c(sep, -sep)+0.5, linetype = 2, alpha = trans) +
  # Actual boundaries
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = prob.delta.paret, color = 'paret'), 
               breaks = 0.5, linetype = 1, size = ln.sz) +
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = prob.delta.adapt, color = 'adapt'), 
               breaks = 0.5, linetype = 1, size = ln.sz) +
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = prob.delta.rando, color = 'rando'), 
               breaks = 0.5, linetype = 1, size = ln.sz) +
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = delta, color = 'tru'), 
               breaks = 0.5, linetype = 1, size = ln.sz) +
  scale_color_manual(breaks = c('tru', 'paret', 'adapt', 'rando'),
                     labels = c('tru' = 'True Boundary', 'paret' = 'Starting Dataset', 
                                'adapt' = '+ Adaptive Sampling', 'rando' = '+ Random Sampling'),
                     values = c('tru' = 'black', 'paret' = 'skyblue2', 'adapt' = 'red', 'rando' = 'green')) +
  scale_fill_manual(breaks = c('tru', 'paret', 'adapt', 'rando'),
                     labels = c('tru' = 'True Boundary', 'paret' = 'Starting Dataset', 
                                'adapt' = '+ Adaptive Sampling', 'rando' = '+ Random Sampling'),
                     values = c('tru' = 'black', 'paret' = 'skyblue2', 'adapt' = 'red', 'rando' = 'green')) +
  labs(x = expression('x'[1]), x = expression('x'[2]), subtitle = 'Criteria: Pareto Distance', color = '') +
  theme_classic() + guides(fill = FALSE) + theme(legend.position = c(0.85, 0.85)) +
  scale_x_continuous(limits = c(0, 5), expand = c(0, 0)) + scale_y_continuous(limits = c(0, 5), expand = c(0, 0))
`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.

## Threshold
ggplot() +
  # Boundaries: +/- some separation from 0.5
  geom_contour_filled(data = fine.grid, mapping = aes(x = x1, y = x2, z = prob.cutof.paret, 
               fill = 'paret'), 
               breaks = c(sep, -sep)+0.5, linetype = 2, alpha = trans) +
  geom_contour_filled(data = fine.grid, mapping = aes(x = x1, y = x2, z = prob.cutof.adapt, 
               fill = 'adapt'), 
               breaks = c(sep, -sep)+0.5, linetype = 2, alpha = trans) +
  geom_contour_filled(data = fine.grid, mapping = aes(x = x1, y = x2, z = prob.cutof.rando, 
               fill = 'rando'), 
               breaks = c(sep, -sep)+0.5, linetype = 2, alpha = trans) +
  # Actual boundaries
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = prob.cutof.paret, color = 'paret'), 
               breaks = 0.5, linetype = 1, size = ln.sz) +
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = prob.cutof.adapt, color = 'adapt'), 
               breaks = 0.5, linetype = 1, size = ln.sz) +
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = prob.cutof.rando, color = 'rando'), 
               breaks = 0.5, linetype = 1, size = ln.sz) +
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = cutof, color = 'tru'), 
               breaks = 0.5, linetype = 1, size = ln.sz) +
  scale_color_manual(breaks = c('tru', 'paret', 'adapt', 'rando'),
                     labels = c('tru' = 'True Boundary', 'paret' = 'Starting Dataset', 
                                'adapt' = '+ Adaptive Sampling', 'rando' = '+ Random Sampling'),
                     values = c('tru' = 'black', 'paret' = 'skyblue2', 'adapt' = 'red', 'rando' = 'green')) +
  scale_fill_manual(breaks = c('tru', 'paret', 'adapt', 'rando'),
                     labels = c('tru' = 'True Boundary', 'paret' = 'Starting Dataset', 
                                'adapt' = '+ Adaptive Sampling', 'rando' = '+ Random Sampling'),
                     values = c('tru' = 'black', 'paret' = 'skyblue2', 'adapt' = 'red', 'rando' = 'green')) +
  labs(x = expression('x'[1]), x = expression('x'[2]), subtitle = 'Criteria: Objective function values', color = '') +
  theme_classic() + guides(fill = FALSE) + theme(legend.position = c(0.85, 0.85)) +
  scale_x_continuous(limits = c(0, 5), expand = c(0, 0)) + scale_y_continuous(limits = c(0, 5), expand = c(0, 0))
`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.

## Radius-angle
ggplot() +
  # Boundaries: +/- some separation from 0.5
  geom_contour_filled(data = fine.grid, mapping = aes(x = x1, y = x2, z = prob.radan.paret, 
               fill = 'paret'), 
               breaks = c(sep, -sep)+0.5, linetype = 2, alpha = trans) +
  geom_contour_filled(data = fine.grid, mapping = aes(x = x1, y = x2, z = prob.radan.adapt, 
               fill = 'adapt'), 
               breaks = c(sep, -sep)+0.5, linetype = 2, alpha = trans) +
  geom_contour_filled(data = fine.grid, mapping = aes(x = x1, y = x2, z = prob.radan.rando, 
               fill = 'rando'), 
               breaks = c(sep, -sep)+0.5, linetype = 2, alpha = trans) +
  # Actual boundaries
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = prob.radan.paret, color = 'paret'), 
               breaks = 0.5, linetype = 1, size = ln.sz) +
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = prob.radan.adapt, color = 'adapt'), 
               breaks = 0.5, linetype = 1, size = ln.sz) +
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = prob.radan.rando, color = 'rando'), 
               breaks = 0.5, linetype = 1, size = ln.sz) +
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = radan, color = 'tru'), 
               breaks = 0.5, linetype = 1, size = ln.sz) +
  scale_color_manual(breaks = c('tru', 'paret', 'adapt', 'rando'),
                     labels = c('tru' = 'True Boundary', 'paret' = 'Starting Dataset', 
                                'adapt' = '+ Adaptive Sampling', 'rando' = '+ Random Sampling'),
                     values = c('tru' = 'black', 'paret' = 'skyblue2', 'adapt' = 'red', 'rando' = 'green')) +
  scale_fill_manual(breaks = c('tru', 'paret', 'adapt', 'rando'),
                     labels = c('tru' = 'True Boundary', 'paret' = 'Starting Dataset', 
                                'adapt' = '+ Adaptive Sampling', 'rando' = '+ Random Sampling'),
                     values = c('tru' = 'black', 'paret' = 'skyblue2', 'adapt' = 'red', 'rando' = 'green')) +
  labs(x = expression('x'[1]), x = expression('x'[2]), color = '',
       subtitle = expression('Criteria: Utopia point distance and f'[1]*' Priority > 80%')) +
  theme_classic() + guides(fill = FALSE) + theme(legend.position = c(0.85, 0.85)) +
  scale_x_continuous(limits = c(0, 5), expand = c(0, 0)) + scale_y_continuous(limits = c(0, 5), expand = c(0, 0))
`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.

The error rate in the GP model estimate should account for the built-in uncertainty in the model. The probability that the model gives the wrong result given the \((x_1, x_2)\) coordinates is the probability that it accepts the point when it should reject it, or vice versa. Since all \((x_1, x_2)\) are equally likely in this mathematical function, the error rate of the GP model is the average of these probabilities. There should be an evident decrease in the error rate between the pre- and post-refinement models.

The Monte Carlo uncertainty in the error rate is \(\sqrt{\frac{p(1-p)}{N}}\).

To save on computational cost, using the fine grid as a surrogate for Monte Carlo sampling. The calculation separates the terms into type I (P[should reject | accepted]) and type II error (P[should accept | rejected]) error rates. These conditionals are calcultaed by Bayes rule, \(P[X|Y] = P[X, Y] P[X] / P[Y]\)

error.rate = data.frame(method = 'GP', source = rep(c('0paret', '1adapt', '2rando'), 3),
                        criteria = c(rep('Pareto Distance', 3), 
                                     rep('Threshold Cutoff', 3), 
                                     rep('Utopia Distance', 3)))
error.rate$criteria = factor(error.rate$criteria, levels = c("Pareto Distance", 
                                     "Threshold Cutoff", "Utopia Distance"))
# Total error rate
error.rate$rate =
  c(mean(c(1 - filter(fine.grid, delta == 1)$prob.delta.paret, filter(fine.grid, delta == 0)$prob.delta.paret)),
    mean(c(1 - filter(fine.grid, delta == 1)$prob.delta.adapt, filter(fine.grid, delta == 0)$prob.delta.adapt)),
    mean(c(1 - filter(fine.grid, delta == 1)$prob.delta.rando, filter(fine.grid, delta == 0)$prob.delta.rando)),
    mean(c(1 - filter(fine.grid, cutof == 1)$prob.cutof.paret, filter(fine.grid, cutof == 0)$prob.cutof.paret)),
    mean(c(1 - filter(fine.grid, cutof == 1)$prob.cutof.adapt, filter(fine.grid, cutof == 0)$prob.cutof.adapt)),
    mean(c(1 - filter(fine.grid, cutof == 1)$prob.cutof.rando, filter(fine.grid, cutof == 0)$prob.cutof.rando)),
    mean(c(1 - filter(fine.grid, radan == 1)$prob.radan.paret, filter(fine.grid, radan == 0)$prob.radan.paret)),
    mean(c(1 - filter(fine.grid, radan == 1)$prob.radan.adapt, filter(fine.grid, radan == 0)$prob.radan.adapt)),
    mean(c(1 - filter(fine.grid, radan == 1)$prob.radan.rando, filter(fine.grid, radan == 0)$prob.radan.rando)))
error.rate$err = sqrt(error.rate$rate*(1-error.rate$rate)/nrow(fine.grid))

# Type 2 error = P[should accept | rejected]
error.rate$typ2 =
  c(sum(1 - filter(fine.grid, delta == 1)$prob.delta.paret)*nrow(filter(fine.grid, delta == 1))/nrow(fine.grid)^2/
      mean(1 - fine.grid$prob.delta.paret),
    sum(1 - filter(fine.grid, delta == 1)$prob.delta.adapt)*nrow(filter(fine.grid, delta == 1))/nrow(fine.grid)^2/
      mean(1 - fine.grid$prob.delta.adapt),
    sum(1 - filter(fine.grid, delta == 1)$prob.delta.rando)*nrow(filter(fine.grid, delta == 1))/nrow(fine.grid)^2/
      mean(1 - fine.grid$prob.delta.rando),
    sum(1 - filter(fine.grid, cutof == 1)$prob.cutof.paret)*nrow(filter(fine.grid, cutof == 1))/nrow(fine.grid)^2/
      mean(1 - fine.grid$prob.cutof.paret),
    sum(1 - filter(fine.grid, cutof == 1)$prob.cutof.adapt)*nrow(filter(fine.grid, cutof == 1))/nrow(fine.grid)^2/
      mean(1 - fine.grid$prob.cutof.adapt),
    sum(1 - filter(fine.grid, cutof == 1)$prob.cutof.rando)*nrow(filter(fine.grid, cutof == 1))/nrow(fine.grid)^2/
      mean(1 - fine.grid$prob.cutof.rando),
    sum(1 - filter(fine.grid, radan == 1)$prob.radan.paret)*nrow(filter(fine.grid, radan == 1))/nrow(fine.grid)^2/
      mean(1 - fine.grid$prob.radan.paret),
    sum(1 - filter(fine.grid, radan == 1)$prob.radan.adapt)*nrow(filter(fine.grid, radan == 1))/nrow(fine.grid)^2/
      mean(1 - fine.grid$prob.radan.adapt),
    sum(1 - filter(fine.grid, radan == 1)$prob.radan.rando)*nrow(filter(fine.grid, radan == 1))/nrow(fine.grid)^2/
      mean(1 - fine.grid$prob.radan.rando))
error.rate$typ2.err = sqrt(error.rate$typ2*(1-error.rate$typ2) / nrow(fine.grid))
                       # c(rep(nrow(filter(fine.grid, delta == 0)), 3),
                       #   rep(nrow(filter(fine.grid, cutof == 0)), 3),
                       #   rep(nrow(filter(fine.grid, radan == 0)), 3)))

# Type 1 error = P[should reject | accepted]
error.rate$typ1 =
  c(sum(filter(fine.grid, delta == 0)$prob.delta.paret)*nrow(filter(fine.grid, delta == 0))/nrow(fine.grid)^2/
      mean(fine.grid$prob.delta.paret),
    sum(filter(fine.grid, delta == 0)$prob.delta.adapt)*nrow(filter(fine.grid, delta == 0))/nrow(fine.grid)^2/
      mean(fine.grid$prob.delta.adapt),
    sum(filter(fine.grid, delta == 0)$prob.delta.rando)*nrow(filter(fine.grid, delta == 0))/nrow(fine.grid)^2/
      mean(fine.grid$prob.delta.rando),
    sum(filter(fine.grid, cutof == 0)$prob.cutof.paret)*nrow(filter(fine.grid, cutof == 0))/nrow(fine.grid)^2/
      mean(fine.grid$prob.cutof.paret),
    sum(filter(fine.grid, cutof == 0)$prob.cutof.adapt)*nrow(filter(fine.grid, cutof == 0))/nrow(fine.grid)^2/
      mean(fine.grid$prob.cutof.adapt),
    sum(filter(fine.grid, cutof == 0)$prob.cutof.rando)*nrow(filter(fine.grid, cutof == 0))/nrow(fine.grid)^2/
      mean(fine.grid$prob.cutof.rando),
    sum(filter(fine.grid, radan == 0)$prob.radan.paret)*nrow(filter(fine.grid, radan == 0))/nrow(fine.grid)^2/
      mean(fine.grid$prob.radan.paret),
    sum(filter(fine.grid, radan == 0)$prob.radan.adapt)*nrow(filter(fine.grid, radan == 0))/nrow(fine.grid)^2/
      mean(fine.grid$prob.radan.adapt),
    sum(filter(fine.grid, radan == 0)$prob.radan.rando)*nrow(filter(fine.grid, radan == 0))/nrow(fine.grid)^2/
      mean(fine.grid$prob.radan.rando))
error.rate$typ1.err = sqrt(error.rate$typ1*(1-error.rate$typ1) / nrow(fine.grid))
                       # c(rep(nrow(filter(fine.grid, delta == 0)), 3),
                       #   rep(nrow(filter(fine.grid, cutof == 0)), 3),
                       #   rep(nrow(filter(fine.grid, radan == 0)), 3)))
error.rate

g.tot = ggplot(error.rate) +
  geom_col(mapping = aes(x = source, y = rate, fill = source)) +
  geom_errorbar(mapping = aes(x = source, y = rate, ymin = rate - err, ymax = rate + err), width = 0.5) +
  facet_wrap(~criteria, scales = 'free') +
  scale_x_discrete(breaks = c(), labels = c()) +
  labs(x = '', y = 'Total\nError Rate') +
  scale_fill_manual(labels = c('0paret' = 'Starting Dataset', '1adapt' = '+ Adaptive Sampling', 
                                 '2rando' = '+ Random Sampling'), name = '',
                      values = c('0paret' = 'skyblue2', '1adapt' = 'red', '2rando' = 'green')) +
  theme_classic() + scale_y_continuous(expand = expansion(mult = c(0, .1)))
g.ty1 = ggplot(error.rate) +
  geom_col(mapping = aes(x = source, y = typ1, fill = source)) +
  geom_errorbar(mapping = aes(x = source, y = typ1, ymin = typ1 - typ1.err, ymax = typ1 + typ1.err), width = 0.5) +
  facet_wrap(~criteria, scales = 'free') +
  scale_x_discrete(breaks = c(), labels = c()) +
  labs(x = '', y = 'False Positive\nError Rate') +
  scale_fill_manual(labels = c('0paret' = 'Starting Dataset', '1adapt' = '+ Adaptive Sampling', 
                                 '2rando' = '+ Random Sampling'), name = '',
                      values = c('0paret' = 'skyblue2', '1adapt' = 'red', '2rando' = 'green')) +
  theme_classic() + scale_y_continuous(expand = expansion(mult = c(0, .1)))
g.ty2 = ggplot(error.rate) +
  geom_col(mapping = aes(x = source, y = typ2, fill = source)) +
  geom_errorbar(mapping = aes(x = source, y = typ2, ymin = typ2 - typ2.err, ymax = typ2 + typ2.err), width = 0.5) +
  facet_wrap(~criteria, scales = 'free') +
  scale_x_discrete(breaks = c(), labels = c()) +
  labs(x = '', y = 'False Negative\nError Rate') +
  scale_fill_manual(labels = c('0paret' = 'Starting Dataset', '1adapt' = '+ Adaptive Sampling', 
                                 '2rando' = '+ Random Sampling'), name = '',
                      values = c('0paret' = 'skyblue2', '1adapt' = 'red', '2rando' = 'green')) +
  theme_classic() + scale_y_continuous(expand = expansion(mult = c(0, .1)))

(g.tot + guides(fill = FALSE)) / g.ty1 / (g.ty2 + guides(fill = FALSE))
`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.

write.csv(error.rate, 'ErrorRates.csv', row.names = F)
rm(g.tot, g.ty1, g.ty2)

A closer inspection of each GP-discovered boundary compared to the actual boundary based on fine-resolution function evaluation.

# Single variable conditionals compared to the expected conditionals by integration
Infer.plt = read.csv('../Ex_Quartic/Marginals_delta.csv')
ggplot() +
  geom_path(data = filter(Infer.plt, cat == 'tru'), mapping = aes(x = x, y = prob - psd, color = cat), linetype = 2) +
  geom_path(data = filter(Infer.plt, cat == 'tru'), mapping = aes(x = x, y = prob + psd, color = cat), linetype = 2) +
  geom_path(data = filter(Infer.plt, cat != 'tru'), mapping = aes(x = x, y = prob, color = cat)) +
  facet_wrap(var~., nrow = 2) + 
  scale_color_manual(labels = c('tru' = 'Expected Marginal', 'start' = 'Starting Dataset', 
                                  'adapt' = '+ Adaptive Sampling', 'rando' = '+ Random Sampling'),
                     values = c('tru' = 'black', 'start' = 'skyblue2', 
                                  'adapt' = 'red', 'rando' = 'green'),
                     breaks = c('tru', 'start', 'adapt', 'rando')) +
  guides(color = guide_legend(override.aes = list(linetype = c(2, 1, 1, 1)))) +
  labs(x = '', y = 'Probability of Acceptance', subtitle = expression('Pareto Distance'), color = '') +
  scale_y_continuous(expand = c(0, 0.05)) + scale_x_continuous(expand = c(0, 0)) +
  theme_bw()


Infer.plt = read.csv('../Ex_Quartic/Marginals_cutof.csv')
ggplot() +
  geom_path(data = filter(Infer.plt, cat == 'tru'), mapping = aes(x = x, y = prob - psd, color = cat), linetype = 2) +
  geom_path(data = filter(Infer.plt, cat == 'tru'), mapping = aes(x = x, y = prob + psd, color = cat), linetype = 2) +
  geom_path(data = filter(Infer.plt, cat != 'tru'), mapping = aes(x = x, y = prob, color = cat)) +
  facet_wrap(var~., nrow = 2) + 
  scale_color_manual(labels = c('tru' = 'Expected Marginal', 'start' = 'Starting Dataset', 
                                  'adapt' = '+ Adaptive Sampling', 'rando' = '+ Random Sampling'),
                     values = c('tru' = 'black', 'start' = 'skyblue2', 
                                  'adapt' = 'red', 'rando' = 'green'),
                     breaks = c('tru', 'start', 'adapt', 'rando')) +
  guides(color = guide_legend(override.aes = list(linetype = c(2, 1, 1, 1)))) +
  labs(x = '', y = 'Probability of Acceptance', subtitle = expression('Cutoff Thresholds'), color = '') +
  scale_y_continuous(expand = c(0, 0.05)) + scale_x_continuous(expand = c(0, 0)) +
  theme_bw()


Infer.plt = read.csv('../Ex_Quartic/Marginals_radan.csv')
ggplot() +
  geom_path(data = filter(Infer.plt, cat == 'tru'), mapping = aes(x = x, y = prob - psd, color = cat), linetype = 2) +
  geom_path(data = filter(Infer.plt, cat == 'tru'), mapping = aes(x = x, y = prob + psd, color = cat), linetype = 2) +
  geom_path(data = filter(Infer.plt, cat != 'tru'), mapping = aes(x = x, y = prob, color = cat)) +
  facet_wrap(var~., nrow = 2, labeller = label_parsed) + 
  scale_color_manual(labels = c('tru' = 'Expected Marginal', 'start' = 'Starting Dataset', 
                                  'adapt' = '+ Adaptive Sampling', 'rando' = '+ Random Sampling'),
                     values = c('tru' = 'black', 'start' = 'skyblue2', 
                                  'adapt' = 'red', 'rando' = 'green'),
                     breaks = c('tru', 'start', 'adapt', 'rando')) +
  guides(color = guide_legend(override.aes = list(linetype = c(2, 1, 1, 1)))) +
  labs(x = '', y = 'Probability of Acceptance', subtitle = expression('Utopia Distance + Priority'), color = '') +
  scale_y_continuous(expand = c(0, 0.05)) + scale_x_continuous(expand = c(0, 0)) +
  theme_bw()

NA
NA

Alternative Metric: Accuracy as n samples are collected

Existing Method: Support Vector Machines

SVM methods require existing labels for the points; in this case, the labels are whether or not the point meets the same three selection criteria as tested with the new GP method.

While all 4 kernel types available in the e1071 package are calculated, the linear and sigmoidal kernels consistently gave poor results, and are excluded from the accuracy calculation.

# Create a function to output the plot of all 4 models compared to the real result
SVM.input = function(fine.input, mod.rad, mod.lin, mod.pol, mod.sig, tit){
  # Fine input has the variables x1, x2, and cat
  res = predict(object = mod.lin, newdata = fine.input[, c('x1', 'x2')])
  fine.input$res = res
  g.lin = ggplot(fine.input) +
    geom_point(data = fine.input, mapping = aes(x = x1, y = x2, color = res)) +
    geom_contour(data = fine.input, mapping = aes(x = x1, y = x2, z = cat), breaks = c(0.5)) +
    labs(subtitle = 'Linear', x = expression('x'[1]), y = expression('x'[2])) + 
    guides(color = FALSE)
  res = predict(object = mod.rad, newdata = fine.input[,c('x1', 'x2')])
  fine.input$res = res
  g.rad = ggplot() +
    geom_point(data = fine.input, mapping = aes(x = x1, y = x2, color = res)) +
    geom_contour(data = fine.input, mapping = aes(x = x1, y = x2, z = cat), breaks = c(0.5)) +
    labs(subtitle = 'Radial', x = expression('x'[1]), y = expression('x'[2]), title = tit) + guides(color = FALSE)
  res = predict(object = mod.pol, newdata = fine.input[,c('x1', 'x2')])
  fine.input$res = res
  g.pol = ggplot() +
    geom_point(data = fine.input, mapping = aes(x = x1, y = x2, color = res)) +
    geom_contour(data = fine.input, mapping = aes(x = x1, y = x2, z = cat), breaks = c(0.5)) +
    labs(subtitle = 'Polynomial', x = expression('x'[1]), y = expression('x'[2])) + guides(color = FALSE)
  res = predict(object = mod.sig, newdata = fine.input[,c('x1', 'x2')])
  fine.input$res = res
  g.sig = ggplot() +
    geom_point(data = fine.input, mapping = aes(x = x1, y = x2, color = res)) +
    geom_contour(data = fine.input, mapping = aes(x = x1, y = x2, z = cat), breaks = c(0.5)) +
    labs(subtitle = 'Sigmoid', x = expression('x'[1]), y = expression('x'[2])) + guides(color = FALSE)
  return((g.rad + g.lin) / (g.pol + g.sig))
}

SVM.mod.all = function(input.data){
  # Given input data, find all 4 default models and return as a list
  fit.rad = e1071::svm(as.factor(cat) ~ x1*x2, data = input.data[,c('x1', 'x2', 'cat')], 
            scale = FALSE, kernel = "radial", cost = 5)
  fit.lin = e1071::svm(as.factor(cat) ~ x1*x2, data = input.data[,c('x1', 'x2', 'cat')], 
            scale = FALSE, kernel = "linear", cost = 5)
  # Polynomial: increase the kernel degree due to complexity of the boundary; maximum tested that still achieved convergence
  fit.pol = e1071::svm(as.factor(cat) ~ x1*x2, data = input.data[,c('x1', 'x2', 'cat')], 
            scale = FALSE, kernel = "polynomial", cost = 5, degree = 3.5)
  # Increase the coefficient
  fit.sig = e1071::svm(as.factor(cat) ~ x1*x2, data = input.data[,c('x1', 'x2', 'cat')], 
            scale = FALSE, kernel = "sigmoid", cost = 5)
  return(list(rad = fit.rad, lin = fit.lin, pol = fit.pol, sig = fit.sig))
}
SVM.err = function(fine.input, mod.list){
  # Fine grid has the real result; mod.list is the 4 different SVM kernels
  fine.input$rad = predict(object = mod.list$rad, newdata = fine.input[,c('x1', 'x2')])
  fine.input$lin = predict(object = mod.list$lin, newdata = fine.input[,c('x1', 'x2')])
  fine.input$pol = predict(object = mod.list$pol, newdata = fine.input[,c('x1', 'x2')])
  # fine.input$sig = predict(object = mod.list$sig, newdata = fine.input[,c('x1', 'x2')])
  
  # Error rate
  rad = nrow(dplyr::filter(fine.input, cat == 0, rad == 1)) + 
    nrow(dplyr::filter(fine.input, cat == 1, rad == 0))
  lin = nrow(dplyr::filter(fine.input, cat == 0, lin == 1)) + 
    nrow(dplyr::filter(fine.input, cat == 1, lin == 0))
  pol = nrow(dplyr::filter(fine.input, cat == 0, pol == 1)) + 
    nrow(dplyr::filter(fine.input, cat == 1, pol == 0))
  # sig = nrow(filter(fine.input, cat == 0, sig == 1)) + nrow(filter(fine.input, cat == 1, sig == 0))
  # rate = c(rad, lin, pol, sig)/nrow(fine.input)
  rate = c(rad, lin, pol)/nrow(fine.input)
  # Uncertainty
  err = sqrt(rate*(1-rate)/nrow(fine.input))
  
  # Type I error = P[should reject | accepted] = P[both] P[should reject] / P[accepted]
  typ1 =
    c(nrow(dplyr::filter(fine.input, cat == 0, rad == 1))*
        nrow(dplyr::filter(fine.input, cat == 0))/nrow(fine.input)/
        nrow(dplyr::filter(fine.input, rad == 1)),
      nrow(dplyr::filter(fine.input, cat == 0, lin == 1))*
        nrow(dplyr::filter(fine.input, cat == 0))/nrow(fine.input)/
        nrow(dplyr::filter(fine.input, lin == 1)),
      nrow(dplyr::filter(fine.input, cat == 0, pol == 1))*
        nrow(dplyr::filter(fine.input, cat == 0))/nrow(fine.input)/
        nrow(dplyr::filter(fine.input, pol == 1)))#,
      # nrow(filter(fine.input, cat == 0, sig == 1))*nrow(filter(fine.input, cat == 0))/nrow(fine.input)/
        # nrow(filter(fine.input, sig == 1)) )
  typ1.err = sqrt(typ1*(1-typ1) / nrow(fine.input))  
  
  # Type II error = P[should accept | rejected] = P[both] P[should accept] / P[rejected]
  typ2 =
    c(nrow(dplyr::filter(fine.input, cat == 1, rad == 0))*
        nrow(dplyr::filter(fine.input, cat == 1))/nrow(fine.input)/
        nrow(dplyr::filter(fine.input, rad == 0)),
      nrow(dplyr::filter(fine.input, cat == 1, lin == 0))*
        nrow(dplyr::filter(fine.input, cat == 1))/nrow(fine.input)/
        nrow(dplyr::filter(fine.input, lin == 0)),
      nrow(dplyr::filter(fine.input, cat == 1, pol == 0))*
        nrow(dplyr::filter(fine.input, cat == 1))/nrow(fine.input)/
        nrow(dplyr::filter(fine.input, pol == 0)))#,
      # nrow(filter(fine.input, cat == 1, sig == 0))*nrow(filter(fine.input, cat == 1))/nrow(fine.input)/
        # nrow(filter(fine.input, sig == 0)) )
  typ2.err = sqrt(typ2*(1-typ2) / nrow(fine.input))
  # return(data.frame(method = c('SVM-rad', 'SVM-lin', 'SVM-pol', 'SVM-sig'), rate, err, typ1, typ1.err, typ2, typ2.err))
  return(data.frame(method = c('SVM-rad', 'SVM-lin', 'SVM-pol'), rate, err, typ1, typ1.err, typ2, typ2.err))
}

# For the radial and polynomial kernels, calculate the marginals and Shapley values for comparison
marginal = function(mod){
  x.rng = seq(from = 0, to = 5, length.out = 50)
  svm.margin = data.frame()
  nsamp = 2000
  for(x in x.rng){
    # x1 marginal
    temp.frame = data.frame(x1 = x, x2 = runif(n = nsamp, min = 0, max = 5))
    res = as.numeric(predict(object = mod$pol, newdata = temp.frame))-1
    svm.margin = rbind(svm.margin, data.frame(x = x, var = 'x1', prob = sum(res)/length(res), method = 'SVM-pol'))
    res = as.numeric(predict(object = mod$rad, newdata = temp.frame))-1
    svm.margin = rbind(svm.margin, data.frame(x = x, var = 'x1', prob = sum(res)/length(res), method = 'SVM-rad'))
    # x1 marginal
    temp.frame = data.frame(x2 = x, x1 = runif(n = nsamp, min = 0, max = 5))
    res = as.numeric(predict(object = mod$pol, newdata = temp.frame))-1
    svm.margin = rbind(svm.margin, data.frame(x = x, var = 'x2', prob = sum(res)/length(res), method = 'SVM-pol'))
    res = as.numeric(predict(object = mod$rad, newdata = temp.frame))-1
    svm.margin = rbind(svm.margin, data.frame(x = x, var = 'x2', prob = sum(res)/length(res), method = 'SVM-rad'))
  }
  
  svm.margin$psd = sqrt(svm.margin$prob*(1 - svm.margin$prob)/nsamp)
  return(svm.margin)
}

SVM.shap = function(mod){
  # Strumbelj et al. (2014) Monte Carlo estimate.
  # Since this is a classification setting, only a difference of 0 or 1 is possible 
  # i.e. a difference of -1 is the same as a difference of 1, as it is simply a misclassification
  nsamp = 1500*50 # Same number of samples as other methods for consistency
  x0 = data.frame(x1 = runif(n = nsamp, min = 0, max = 5),
                  x2 = runif(n = nsamp, min = 0, max = 5))
  z1 = data.frame(x1 = x0$x1,
                  x2 = runif(n = nsamp, min = 0, max = 5))
  z2 = data.frame(x1 = runif(n = nsamp, min = 0, max = 5),
                  x2 = x0$x2)
  # Apply functions to all
  res.pol = as.numeric(predict(object = mod$pol, newdata = x0))
  res.rad = as.numeric(predict(object = mod$rad, newdata = x0))
  x0$pol = res.pol; x0$rad = res.rad
  res.pol = as.numeric(predict(object = mod$pol, newdata = z1))
  res.rad = as.numeric(predict(object = mod$rad, newdata = z1))
  z1$pol = res.pol; z1$rad = res.rad
  res.pol = as.numeric(predict(object = mod$pol, newdata = z2))
  res.rad = as.numeric(predict(object = mod$rad, newdata = z2))
  z2$pol = res.pol; z2$rad = res.rad
  # Calculate mean and standard error of the differences for Shapley values
  shap = c(mean((x0$pol - z1$pol)), mean((x0$pol - z2$pol)),
           mean((x0$rad - z1$rad)), mean((x0$rad - z2$rad)))
  r.shap = c(shap[1:2]/max(shap[1:2]), shap[3:4]/max(shap[3:4]))
  # For standard error, calculate standard error of the mean with n = 1500
  # to be consistent with the other standard errors
  pol.x1m = apply(matrix(x0$pol - z1$pol, nrow = 1500), 2, mean)
  rad.x1m = apply(matrix(x0$rad - z1$rad, nrow = 1500), 2, mean)
  pol.x2m = apply(matrix(x0$pol - z2$pol, nrow = 1500), 2, mean)
  rad.x2m = apply(matrix(x0$rad - z2$rad, nrow = 1500), 2, mean)
  
  shap.err = c(sd(pol.x1m), sd(pol.x2m),
               sd(rad.x1m), sd(rad.x2m))
  return(data.frame(import = shap, var = c('x1', 'x2'), 
                    sd = shap.err, r.import = r.shap, 
                    method = c('Shapley-pol', 'Shapley-pol', 
                               'Shapley-rad', 'Shapley-rad')))
}
data.paret = read.csv('../Ex_Quartic/GPar_all_start.csv')
data.delta = read.csv('../Ex_Quartic/GPar_Accept_Delta1.csv')
data.rando = read.csv(file = 'GPar_Random.csv')
data.rando = data.rando[1:nrow(data.paret), ]

# Define acceptance
data.paret$cat = 0; data.delta$cat = 0; data.rando$cat = 0; fine.grid$cat = 0
data.paret$cat[data.paret$dist <= 1] = 1
data.delta$cat[data.delta$dist <= 1] = 1
data.rando$cat[data.rando$dist <= 1] = 1
fine.grid$cat[fine.grid$dist <= 1] = 1

# Plots
mod.paret = SVM.mod.all(input.data = data.paret)
SVM.input(fine.input = fine.grid[,c('x1', 'x2', 'cat')], 
          mod.rad = mod.paret$rad, mod.lin = mod.paret$lin, 
          mod.pol = mod.paret$pol, mod.sig = mod.paret$sig,
          tit = 'Pareto Distance, Starting Dataset')
`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.

mod.adapt = SVM.mod.all(input.data = data.delta)
# Store this model for importance ranking later
mod.adapt.delta = mod.adapt
SVM.input(fine.input = fine.grid[,c('x1', 'x2', 'cat')], 
          mod.rad = mod.adapt$rad, mod.lin = mod.adapt$lin, 
          mod.pol = mod.adapt$pol, mod.sig = mod.adapt$sig,
          tit = 'Pareto Distance, + Adaptive Sampling')
`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.

mod.rando = SVM.mod.all(input.data = data.rando)
SVM.input(fine.input = fine.grid[,c('x1', 'x2', 'cat')], 
          mod.rad = mod.rando$rad, mod.lin = mod.rando$lin, 
          mod.pol = mod.rando$pol, mod.sig = mod.rando$sig,
          tit = 'Pareto Distance, + Random Sampling')
`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.

err.paret = SVM.err(fine.input = fine.grid, mod.list = mod.paret)
err.paret$source = '0paret'; err.paret$criteria = 'Pareto Distance'
err.adapt = SVM.err(fine.input = fine.grid, mod.list = mod.adapt)
err.adapt$source = '1adapt'; err.adapt$criteria = 'Pareto Distance'
err.rando = SVM.err(fine.input = fine.grid, mod.list = mod.rando)
err.rando$source = '2rando'; err.rando$criteria = 'Pareto Distance'

err.svm.dist = rbind(err.paret, err.adapt, err.rando)

g.rate = ggplot(err.svm.dist) +
  geom_col(mapping = aes(x = source, y = rate, fill = source)) +
  geom_errorbar(mapping = aes(x = source, ymin = rate - err, ymax = rate + err), width = 0.5) +
  facet_wrap(~method, nrow = 1) +
  scale_fill_manual(labels = c('0paret' = 'Starting Dataset', '1adapt' = '+ Adaptive Sampling', 
                                 '2rando' = '+ Random Sampling'), name = '',
                      values = c('0paret' = 'skyblue2', '1adapt' = 'red', '2rando' = 'green')) +
  scale_y_continuous(expand = expansion(mult = c(0, 0.05))) + scale_x_discrete(breaks = '') +
  theme_bw() + labs(x = '', y = 'Total\nError Rate', subtitle = 'Pareto Distance')

g.typ1 = ggplot(err.svm.dist) +
  geom_col(mapping = aes(x = source, y = typ1, fill = source)) +
  geom_errorbar(mapping = aes(x = source, ymin = typ1 - typ1.err, ymax = typ1 + typ1.err), width = 0.5) +
  facet_wrap(~method, nrow = 1) +
  scale_fill_manual(labels = c('0paret' = 'Starting Dataset', '1adapt' = '+ Adaptive Sampling', 
                                 '2rando' = '+ Random Sampling'), name = '',
                      values = c('0paret' = 'skyblue2', '1adapt' = 'red', '2rando' = 'green')) +
  scale_y_continuous(expand = expansion(mult = c(0, 0.05))) + scale_x_discrete(breaks = '') +
  theme_bw() + labs(x = '', y = 'False Positive\nError Rate')

g.typ2 = ggplot(err.svm.dist) +
  geom_col(mapping = aes(x = source, y = typ2, fill = source)) +
  geom_errorbar(mapping = aes(x = source, ymin = typ2 - typ2.err, ymax = typ2 + typ2.err), width = 0.5) +
  facet_wrap(~method, nrow = 1) +
  scale_fill_manual(labels = c('0paret' = 'Starting Dataset', '1adapt' = '+ Adaptive Sampling', 
                                 '2rando' = '+ Random Sampling'), name = '',
                      values = c('0paret' = 'skyblue2', '1adapt' = 'red', '2rando' = 'green')) +
  scale_y_continuous(expand = expansion(mult = c(0, 0.05))) + scale_x_discrete(breaks = '') +
  theme_bw() + labs(x = '', y = 'False Negative\nError Rate')

(g.rate + guides(fill = FALSE)) / g.typ1 / (g.typ2 + guides(fill = FALSE))
`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.

write.csv(err.svm.dist, 'SVM_error_delta.csv', row.names = F)
rm(err.paret, err.adapt, err.rando)

Graphically, the selection of the kernel form has a large impact on the quality of the result. The polynomial and radial kernels appear to roughly match the shape of the normalized distance = 1 criterion; the linear and sigmoidal kernels do not appear to match anything. Further tuning of the polynomial degree or coefficient could improve the results, but given the constrained computational budget for sampling, separation into a training and testing set would likely lead to worse results.

svm.margin.paret = marginal(mod = mod.paret); svm.margin.paret$cat = 'start'
svm.margin.adapt = marginal(mod = mod.adapt); svm.margin.adapt$cat = 'adapt'
svm.margin.rando = marginal(mod = mod.rando); svm.margin.rando$cat = 'rando'
svm.margin = rbind(svm.margin.paret, svm.margin.adapt, svm.margin.rando)
rm(svm.margin.paret, svm.margin.adapt, svm.margin.rando)

write.csv(svm.margin, 'Margin_SVM_dist.csv', row.names = F)

Infer.plt = read.csv('../Ex_Quartic/Marginals_delta.csv')
Infer.plt = Infer.plt[,!names(Infer.plt) %in% c('X')]
# names(Infer.plt)
Infer.plt$method = 'GP'

ggplot() +
  geom_path(data = filter(Infer.plt, cat == 'tru'), mapping = aes(x = x, y = prob - psd, color = cat), linetype = 3) +
  geom_path(data = filter(Infer.plt, cat == 'tru'), mapping = aes(x = x, y = prob + psd, color = cat), linetype = 3) +
  geom_path(data = svm.margin, mapping = aes(x = x, y = prob, linetype = method, color = cat)) +
  facet_wrap(~var, nrow = 2) + theme_bw() +
  labs(x = 'Input Value', y = 'Conditional Probability', linetype = 'SVM Kernel', color = '') +
  scale_color_manual(labels = c('tru' = 'Expected Marginal', 'start' = 'Starting Dataset', 
                                  'adapt' = '+ Adaptive Sampling', 'rando' = '+ Random Sampling'),
                     values = c('tru' = 'black', 'start' = 'skyblue2', 
                                  'adapt' = 'red', 'rando' = 'green'),
                     breaks = c('tru', 'start', 'adapt', 'rando')) +
  guides(color = guide_legend(override.aes = list(linetype = c(3, 1, 1, 1)))) +
  scale_linetype_discrete(labels = c('SVM-pol' = 'Polynomial', 'rad' = 'SVM-Radial')) +
  scale_x_continuous(expand = c(0, 0)) + scale_y_continuous(limits = c(0, 1))


ggplot() +
  geom_path(data = filter(Infer.plt, cat == 'tru'), mapping = aes(x = x, y = prob - psd, color = cat), linetype = 3) +
  geom_path(data = filter(Infer.plt, cat == 'tru'), mapping = aes(x = x, y = prob + psd, color = cat), linetype = 3) +
  geom_path(data = filter(Infer.plt, cat != 'tru'), mapping = aes(x = x, y = prob, color = cat)) +
  facet_wrap(var~., nrow = 2) + 
  scale_color_manual(labels = c('tru' = 'Expected Marginal', 'start' = 'Starting Dataset', 
                                  'adapt' = '+ Adaptive Sampling', 'rando' = '+ Random Sampling'),
                     values = c('tru' = 'black', 'start' = 'skyblue2', 
                                  'adapt' = 'red', 'rando' = 'green'),
                     breaks = c('tru', 'start', 'adapt', 'rando')) +
  guides(color = guide_legend(override.aes = list(linetype = c(3, 1, 1, 1)))) +
  labs(x = '', y = 'Probability of Acceptance', subtitle = expression('Pareto Distance'), color = '') +
  scale_y_continuous(expand = c(0, 0.05)) + scale_x_continuous(expand = c(0, 0)) +
  theme_bw()


# Calculate coefficients of determination: easier comparison
coefdet = data.frame(method = c(rep('SVM-pol', 3), rep('SVM-rad', 3), rep('GP', 3)),
           dataset = c('0start', '1adapt', '2rando'),
 coef = c(cor(x = filter(svm.margin, method == 'SVM-pol', cat == 'start')$prob, 
    y = filter(Infer.plt, cat == 'tru')$prob, method = 'pearson')^2,
  cor(x = filter(svm.margin, method == 'SVM-pol', cat == 'adapt')$prob, 
    y = filter(Infer.plt, cat == 'tru')$prob, method = 'pearson')^2,
  cor(x = filter(svm.margin, method == 'SVM-pol', cat == 'rando')$prob, 
    y = filter(Infer.plt, cat == 'tru')$prob, method = 'pearson')^2,
  cor(x = filter(svm.margin, method == 'SVM-rad', cat == 'start')$prob, 
    y = filter(Infer.plt, cat == 'tru')$prob, method = 'pearson')^2,
  cor(x = filter(svm.margin, method == 'SVM-rad', cat == 'adapt')$prob, 
    y = filter(Infer.plt, cat == 'tru')$prob, method = 'pearson')^2,
  cor(x = filter(svm.margin, method == 'SVM-rad', cat == 'rando')$prob, 
    y = filter(Infer.plt, cat == 'tru')$prob, method = 'pearson')^2,
  cor(x = filter(Infer.plt, cat == 'start')$prob, 
    y = c(filter(Infer.plt, cat == 'tru', var == 'x1')$prob, filter(Infer.plt, cat == 'tru', var == 'x2')$prob), 
    method = 'pearson')^2,
  cor(x = filter(Infer.plt, cat == 'adapt')$prob, 
    y = c(filter(Infer.plt, cat == 'tru', var == 'x1')$prob, filter(Infer.plt, cat == 'tru', var == 'x2')$prob), 
    method = 'pearson')^2,
  cor(x = filter(Infer.plt, cat == 'rando')$prob, 
    y = c(filter(Infer.plt, cat == 'tru', var == 'x1')$prob, filter(Infer.plt, cat == 'tru', var == 'x2')$prob), 
    method = 'pearson')^2))

coefdet
write.csv(coefdet, 'Marginals_CoefDet_delta.csv', row.names = F)

ggplot(coefdet) +
  geom_col(mapping = aes(x = dataset, fill = dataset, y = coef))+
  facet_grid(~method) +
  labs(x = '', y = '1-Variable Marginal Coefficient of Determination', subtitle = 'Pareto Distance') +
  scale_x_discrete(breaks = c()) +
  scale_fill_manual(labels = c('0start' = 'Starting Dataset', '1adapt' = '+ Adaptive Sampling', 
                                 '2rando' = '+ Random Sampling'), name = '',
                    values = c('0start' = 'skyblue2', '1adapt' = 'red', '2rando' = 'green')) +
  scale_y_continuous(expand = expansion(mult = c(0, 0.05)))

Single variable marginals are visually similar to the expected value, but calculating the coefficient of determination shows that the GP method is more consistent regardless of dataset and it demonstrates the expected improvement with increased sampling near the boundary. The SVM methods overall cannot capture the boundary very well, leading to classification errors.

data.paret = read.csv('../Ex_Quartic/GPar_all_start.csv')
data.cutof = read.csv('../Ex_Quartic/GPar_Accept_Threshold.csv')
data.rando = read.csv(file = 'GPar_Random.csv')
data.rando = data.rando[1:nrow(data.paret), ]

# Define acceptance
data.paret$cat = 0; data.cutof$cat = 0; data.rando$cat = 0; fine.grid$cat = 0
data.paret$cat[data.paret$f1.norm <= 1 & data.paret$f2.norm <= 1] = 1
data.cutof$cat[data.cutof$f1.norm <= 1 & data.cutof$f2.norm <= 1] = 1
data.rando$cat[data.rando$f1.norm <= 1 & data.rando$f2.norm <= 1] = 1
fine.grid$cat[fine.grid$f1.norm <= 1 & fine.grid$f2.norm <= 1] = 1

# Plots
mod.paret = SVM.mod.all(input.data = data.paret)
SVM.input(fine.input = fine.grid[,c('x1', 'x2', 'cat')], 
          mod.rad = mod.paret$rad, mod.lin = mod.paret$lin, 
          mod.pol = mod.paret$pol, mod.sig = mod.paret$sig,
          tit = 'Threshold Cutoff, Starting Dataset')
`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.

mod.adapt = SVM.mod.all(input.data = data.cutof)
mod.adapt.cutof = mod.adapt
SVM.input(fine.input = fine.grid[,c('x1', 'x2', 'cat')], 
          mod.rad = mod.adapt$rad, mod.lin = mod.adapt$lin, 
          mod.pol = mod.adapt$pol, mod.sig = mod.adapt$sig,
          tit = 'Threshold Cutoff, + Adaptive Sampling')
`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.

mod.rando = SVM.mod.all(input.data = data.rando)
SVM.input(fine.input = fine.grid[,c('x1', 'x2', 'cat')], 
          mod.rad = mod.rando$rad, mod.lin = mod.rando$lin, 
          mod.pol = mod.rando$pol, mod.sig = mod.rando$sig,
          tit = 'Threshold Cutoff, + Random Sampling')
`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.

# Error rates
err.paret = SVM.err(fine.input = fine.grid, mod.list = mod.paret)
err.paret$source = '0paret'; err.paret$criteria = 'Threshold Cutoff'
err.adapt = SVM.err(fine.input = fine.grid, mod.list = mod.adapt)
err.adapt$source = '1adapt'; err.adapt$criteria = 'Threshold Cutoff'
err.rando = SVM.err(fine.input = fine.grid, mod.list = mod.rando)
err.rando$source = '2rando'; err.rando$criteria = 'Threshold Cutoff'

err.svm.cutof = rbind(err.paret, err.adapt, err.rando)

g.rate = ggplot(err.svm.cutof) +
  geom_col(mapping = aes(x = source, y = rate, fill = source)) +
  geom_errorbar(mapping = aes(x = source, ymin = rate - err, ymax = rate + err), width = 0.5) +
  facet_wrap(~method, nrow = 1) +
  scale_fill_manual(labels = c('0paret' = 'Starting Dataset', '1adapt' = '+ Adaptive Sampling', 
                                 '2rando' = '+ Random Sampling'), name = '',
                      values = c('0paret' = 'skyblue2', '1adapt' = 'red', '2rando' = 'green')) +
  scale_y_continuous(expand = expansion(mult = c(0, 0.05))) + scale_x_discrete(breaks = '') +
  theme_bw() + labs(x = '', y = 'Total\nError Rate', subtitle = 'Cutoff Threshold')

g.typ1 = ggplot(err.svm.cutof) +
  geom_col(mapping = aes(x = source, y = typ1, fill = source)) +
  geom_errorbar(mapping = aes(x = source, ymin = typ1 - typ1.err, ymax = typ1 + typ1.err), width = 0.5) +
  facet_wrap(~method, nrow = 1) +
  scale_fill_manual(labels = c('0paret' = 'Starting Dataset', '1adapt' = '+ Adaptive Sampling', 
                                 '2rando' = '+ Random Sampling'), name = '',
                      values = c('0paret' = 'skyblue2', '1adapt' = 'red', '2rando' = 'green')) +
  scale_y_continuous(expand = expansion(mult = c(0, 0.05))) + scale_x_discrete(breaks = '') +
  theme_bw() + labs(x = '', y = 'False Positive\nError Rate')

g.typ2 = ggplot(err.svm.cutof) +
  geom_col(mapping = aes(x = source, y = typ2, fill = source)) +
  geom_errorbar(mapping = aes(x = source, ymin = typ2 - typ2.err, ymax = typ2 + typ2.err), width = 0.5) +
  facet_wrap(~method, nrow = 1) +
  scale_fill_manual(labels = c('0paret' = 'Starting Dataset', '1adapt' = '+ Adaptive Sampling', 
                                 '2rando' = '+ Random Sampling'), name = '',
                      values = c('0paret' = 'skyblue2', '1adapt' = 'red', '2rando' = 'green')) +
  scale_y_continuous(expand = expansion(mult = c(0, 0.05))) + scale_x_discrete(breaks = '') +
  theme_bw() + labs(x = '', y = 'False Negative\nError Rate')

(g.rate + guides(fill = FALSE)) / g.typ1 / (g.typ2 + guides(fill = FALSE))
`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.

write.csv(err.svm.cutof, 'SVM_error_cutof.csv', row.names = F)

rm(err.paret, err.adapt, err.rando, g.rate, g.typ1, g.typ2)
svm.margin.paret = marginal(mod = mod.paret); svm.margin.paret$cat = 'start'
svm.margin.adapt = marginal(mod = mod.adapt); svm.margin.adapt$cat = 'adapt'
svm.margin.rando = marginal(mod = mod.rando); svm.margin.rando$cat = 'rando'
svm.margin = rbind(svm.margin.paret, svm.margin.adapt, svm.margin.rando)
rm(svm.margin.paret, svm.margin.adapt, svm.margin.rando)

write.csv(svm.margin, 'Margin_SVM_cutof.csv', row.names = F)

Infer.plt = read.csv('../Ex_Quartic/Marginals_cutof.csv')
Infer.plt = Infer.plt[,!names(Infer.plt) %in% c('X')]
# names(Infer.plt)
Infer.plt$method = 'GP'

ggplot() +
  geom_path(data = filter(Infer.plt, cat == 'tru'), mapping = aes(x = x, y = prob - psd, color = cat), linetype = 3) +
  geom_path(data = filter(Infer.plt, cat == 'tru'), mapping = aes(x = x, y = prob + psd, color = cat), linetype = 3) +
  geom_path(data = svm.margin, mapping = aes(x = x, y = prob, linetype = method, color = cat)) +
  facet_wrap(~var, nrow = 2) + theme_bw() +
  labs(x = 'Input Value', y = 'Conditional Probability', linetype = 'SVM Kernel', color = '', subtitle = 'Cutoff Threshold') +
  scale_color_manual(labels = c('tru' = 'Expected Marginal', 'start' = 'Starting Dataset', 
                                  'adapt' = '+ Adaptive Sampling', 'rando' = '+ Random Sampling'),
                     values = c('tru' = 'black', 'start' = 'skyblue2', 
                                  'adapt' = 'red', 'rando' = 'green'),
                     breaks = c('tru', 'start', 'adapt', 'rando')) +
  guides(color = guide_legend(override.aes = list(linetype = c(3, 1, 1, 1)))) +
  scale_linetype_discrete(labels = c('SVM-pol' = 'Polynomial', 'rad' = 'SVM-Radial')) +
  scale_x_continuous(expand = c(0, 0)) + scale_y_continuous(limits = c(0, 1))


ggplot() +
  geom_path(data = filter(Infer.plt, cat == 'tru'), mapping = aes(x = x, y = prob - psd, color = cat), linetype = 3) +
  geom_path(data = filter(Infer.plt, cat == 'tru'), mapping = aes(x = x, y = prob + psd, color = cat), linetype = 3) +
  geom_path(data = filter(Infer.plt, cat != 'tru'), mapping = aes(x = x, y = prob, color = cat)) +
  facet_wrap(var~., nrow = 2) + 
  scale_color_manual(labels = c('tru' = 'Expected Marginal', 'start' = 'Starting Dataset', 
                                  'adapt' = '+ Adaptive Sampling', 'rando' = '+ Random Sampling'),
                     values = c('tru' = 'black', 'start' = 'skyblue2', 
                                  'adapt' = 'red', 'rando' = 'green'),
                     breaks = c('tru', 'start', 'adapt', 'rando')) +
  guides(color = guide_legend(override.aes = list(linetype = c(3, 1, 1, 1)))) +
  labs(x = '', y = 'Probability of Acceptance', subtitle = 'Cutoff Threshold', color = '') +
  scale_y_continuous(expand = c(0, 0.05)) + scale_x_continuous(expand = c(0, 0)) +
  theme_bw()


# Calculate coefficients of determination: easier comparison
coefdet = data.frame(method = c(rep('SVM-pol', 3), rep('SVM-rad', 3), rep('GP', 3)),
           dataset = c('0start', '1adapt', '2rando'),
 coef = c(cor(x = filter(svm.margin, method == 'SVM-pol', cat == 'start')$prob, 
    y = filter(Infer.plt, cat == 'tru')$prob, method = 'pearson')^2,
  cor(x = filter(svm.margin, method == 'SVM-pol', cat == 'adapt')$prob, 
    y = filter(Infer.plt, cat == 'tru')$prob, method = 'pearson')^2,
  cor(x = filter(svm.margin, method == 'SVM-pol', cat == 'rando')$prob, 
    y = filter(Infer.plt, cat == 'tru')$prob, method = 'pearson')^2,
  cor(x = filter(svm.margin, method == 'SVM-rad', cat == 'start')$prob, 
    y = filter(Infer.plt, cat == 'tru')$prob, method = 'pearson')^2,
  cor(x = filter(svm.margin, method == 'SVM-rad', cat == 'adapt')$prob, 
    y = filter(Infer.plt, cat == 'tru')$prob, method = 'pearson')^2,
  cor(x = filter(svm.margin, method == 'SVM-rad', cat == 'rando')$prob, 
    y = filter(Infer.plt, cat == 'tru')$prob, method = 'pearson')^2,
  cor(x = filter(Infer.plt, cat == 'start')$prob, 
    y = c(filter(Infer.plt, cat == 'tru', var == 'x1')$prob, filter(Infer.plt, cat == 'tru', var == 'x2')$prob), 
    method = 'pearson')^2,
  cor(x = filter(Infer.plt, cat == 'adapt')$prob, 
    y = c(filter(Infer.plt, cat == 'tru', var == 'x1')$prob, filter(Infer.plt, cat == 'tru', var == 'x2')$prob), 
    method = 'pearson')^2,
  cor(x = filter(Infer.plt, cat == 'rando')$prob, 
    y = c(filter(Infer.plt, cat == 'tru', var == 'x1')$prob, filter(Infer.plt, cat == 'tru', var == 'x2')$prob), 
    method = 'pearson')^2))

coefdet
write.csv(coefdet, 'Marginals_CoefDet_cutof.csv', row.names = F)

ggplot(coefdet) +
  geom_col(mapping = aes(x = dataset, fill = dataset, y = coef))+
  facet_grid(~method) +
  labs(x = '', y = '1-Variable Marginal Coefficient of Determination', subtitle = 'Cutoff Threshold') +
  scale_x_discrete(breaks = c()) +
  scale_fill_manual(labels = c('0start' = 'Starting Dataset', '1adapt' = '+ Adaptive Sampling', 
                                 '2rando' = '+ Random Sampling'), name = '',
                    values = c('0start' = 'skyblue2', '1adapt' = 'red', '2rando' = 'green')) +
  scale_y_continuous(expand = expansion(mult = c(0, 0.05)))

data.paret = read.csv(file = '../Ex_Quartic/GPar_all_start.csv')
data.radan = read.csv('../Ex_Quartic/GPar_Accept_Radius.csv')
data.rando = read.csv(file = 'GPar_Random.csv')
data.rando = data.rando[1:nrow(data.paret), ]

# Define acceptance
data.paret$cat = 0; data.radan$cat = 0; data.rando$cat = 0; fine.grid$cat = 0
data.paret$cat[sqrt(data.paret$f1.norm^2 + data.paret$f2.norm^2) <= 1 & data.paret$theta > 20] = 1
data.radan$cat[sqrt(data.radan$f1.norm^2 + data.radan$f2.norm^2) <= 1 & data.radan$theta > 20] = 1
data.rando$cat[sqrt(data.rando$f1.norm^2 + data.rando$f2.norm^2) <= 1 & data.rando$theta > 20] = 1
fine.grid$cat[sqrt(fine.grid$f1.norm^2 + fine.grid$f2.norm^2) <= 1 & fine.grid$ang > 20] = 1

# Plots
mod.paret = SVM.mod.all(input.data = data.paret)
SVM.input(fine.input = fine.grid[,c('x1', 'x2', 'cat')], 
          mod.rad = mod.paret$rad, mod.lin = mod.paret$lin, 
          mod.pol = mod.paret$pol, mod.sig = mod.paret$sig,
          tit = 'Utopia Distance + Priority, Starting Dataset')
`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.

mod.adapt = SVM.mod.all(input.data = data.radan)
mod.adapt.radan = mod.adapt
SVM.input(fine.input = fine.grid[,c('x1', 'x2', 'cat')], 
          mod.rad = mod.adapt$rad, mod.lin = mod.adapt$lin, 
          mod.pol = mod.adapt$pol, mod.sig = mod.adapt$sig,
          tit = 'Utopia Distance + Priority, + Adaptive Sampling')
`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.

mod.rando = SVM.mod.all(input.data = data.rando)
SVM.input(fine.input = fine.grid[,c('x1', 'x2', 'cat')], 
          mod.rad = mod.rando$rad, mod.lin = mod.rando$lin, 
          mod.pol = mod.rando$pol, mod.sig = mod.rando$sig,
          tit = 'Utopia Distance + Priority, + Random Sampling')
`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.

# Error rates
err.paret = SVM.err(fine.input = fine.grid, mod.list = mod.paret)
err.paret$source = '0paret'; err.paret$criteria = 'Utopia Distance'
err.adapt = SVM.err(fine.input = fine.grid, mod.list = mod.adapt)
err.adapt$source = '1adapt'; err.adapt$criteria = 'Utopia Distance'
err.rando = SVM.err(fine.input = fine.grid, mod.list = mod.rando)
err.rando$source = '2rando'; err.rando$criteria = 'Utopia Distance'

err.svm.radan = rbind(err.paret, err.adapt, err.rando)

g.rate = ggplot(err.svm.radan) +
  geom_col(mapping = aes(x = source, y = rate, fill = source)) +
  geom_errorbar(mapping = aes(x = source, ymin = rate - err, ymax = rate + err), width = 0.5) +
  facet_wrap(~method, nrow = 1) +
  scale_fill_manual(labels = c('0paret' = 'Starting Dataset', '1adapt' = '+ Adaptive Sampling', 
                                 '2rando' = '+ Random Sampling'), name = '',
                      values = c('0paret' = 'skyblue2', '1adapt' = 'red', '2rando' = 'green')) +
  scale_y_continuous(expand = expansion(mult = c(0, 0.05))) + scale_x_discrete(breaks = '') +
  theme_bw() + labs(x = '', y = 'Total\nError Rate', subtitle = 'Utopia Distance + Priority')

g.typ1 = ggplot(err.svm.radan) +
  geom_col(mapping = aes(x = source, y = typ1, fill = source)) +
  geom_errorbar(mapping = aes(x = source, ymin = typ1 - typ1.err, ymax = typ1 + typ1.err), width = 0.5) +
  facet_wrap(~method, nrow = 1) +
  scale_fill_manual(labels = c('0paret' = 'Starting Dataset', '1adapt' = '+ Adaptive Sampling', 
                                 '2rando' = '+ Random Sampling'), name = '',
                      values = c('0paret' = 'skyblue2', '1adapt' = 'red', '2rando' = 'green')) +
  scale_y_continuous(expand = expansion(mult = c(0, 0.05))) + scale_x_discrete(breaks = '') +
  theme_bw() + labs(x = '', y = 'False Positive\nError Rate')

g.typ2 = ggplot(err.svm.radan) +
  geom_col(mapping = aes(x = source, y = typ2, fill = source)) +
  geom_errorbar(mapping = aes(x = source, ymin = typ2 - typ2.err, ymax = typ2 + typ2.err), width = 0.5) +
  facet_wrap(~method, nrow = 1) +
  scale_fill_manual(labels = c('0paret' = 'Starting Dataset', '1adapt' = '+ Adaptive Sampling', 
                                 '2rando' = '+ Random Sampling'), name = '',
                      values = c('0paret' = 'skyblue2', '1adapt' = 'red', '2rando' = 'green')) +
  scale_y_continuous(expand = expansion(mult = c(0, 0.05))) + scale_x_discrete(breaks = '') +
  theme_bw() + labs(x = '', y = 'False Negative\nError Rate')

(g.rate + guides(fill = FALSE)) / g.typ1 / (g.typ2 + guides(fill = FALSE))
`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.

write.csv(err.svm.cutof, 'SVM_error_radan.csv', row.names = F)

rm(err.paret, err.adapt, err.rando, g.rate, g.typ1, g.typ2)
svm.margin.paret = marginal(mod = mod.paret); svm.margin.paret$cat = 'start'
svm.margin.adapt = marginal(mod = mod.adapt); svm.margin.adapt$cat = 'adapt'
svm.margin.rando = marginal(mod = mod.rando); svm.margin.rando$cat = 'rando'
svm.margin = rbind(svm.margin.paret, svm.margin.adapt, svm.margin.rando)
rm(svm.margin.paret, svm.margin.adapt, svm.margin.rando)

write.csv(svm.margin, 'Margin_SVM_radan.csv', row.names = F)

Infer.plt = read.csv('../Ex_Quartic/Marginals_radan.csv')
Infer.plt = Infer.plt[,!names(Infer.plt) %in% c('X')]
# names(Infer.plt)
Infer.plt$method = 'GP'

ggplot() +
  geom_path(data = filter(Infer.plt, cat == 'tru'), mapping = aes(x = x, y = prob - psd, color = cat), linetype = 3) +
  geom_path(data = filter(Infer.plt, cat == 'tru'), mapping = aes(x = x, y = prob + psd, color = cat), linetype = 3) +
  geom_path(data = svm.margin, mapping = aes(x = x, y = prob, linetype = method, color = cat)) +
  facet_wrap(~var, nrow = 2) + theme_bw() +
  labs(x = 'Input Value', y = 'Conditional Probability', linetype = 'SVM Kernel', color = '', 
       subtitle = 'Utopia Distance + Priority') +
  scale_color_manual(labels = c('tru' = 'Expected Marginal', 'start' = 'Starting Dataset', 
                                  'adapt' = '+ Adaptive Sampling', 'rando' = '+ Random Sampling'),
                     values = c('tru' = 'black', 'start' = 'skyblue2', 
                                  'adapt' = 'red', 'rando' = 'green'),
                     breaks = c('tru', 'start', 'adapt', 'rando')) +
  guides(color = guide_legend(override.aes = list(linetype = c(3, 1, 1, 1)))) +
  scale_linetype_discrete(labels = c('SVM-pol' = 'Polynomial', 'rad' = 'SVM-Radial')) +
  scale_x_continuous(expand = c(0, 0)) + scale_y_continuous(limits = c(0, 1))


ggplot() +
  geom_path(data = filter(Infer.plt, cat == 'tru'), mapping = aes(x = x, y = prob - psd, color = cat), linetype = 3) +
  geom_path(data = filter(Infer.plt, cat == 'tru'), mapping = aes(x = x, y = prob + psd, color = cat), linetype = 3) +
  geom_path(data = filter(Infer.plt, cat != 'tru'), mapping = aes(x = x, y = prob, color = cat)) +
  facet_wrap(var~., nrow = 2) + 
  scale_color_manual(labels = c('tru' = 'Expected Marginal', 'start' = 'Starting Dataset', 
                                  'adapt' = '+ Adaptive Sampling', 'rando' = '+ Random Sampling'),
                     values = c('tru' = 'black', 'start' = 'skyblue2', 
                                  'adapt' = 'red', 'rando' = 'green'),
                     breaks = c('tru', 'start', 'adapt', 'rando')) +
  guides(color = guide_legend(override.aes = list(linetype = c(3, 1, 1, 1)))) +
  labs(x = '', y = 'Probability of Acceptance', subtitle = 'Cutoff Threshold', color = '') +
  scale_y_continuous(expand = c(0, 0.05)) + scale_x_continuous(expand = c(0, 0)) +
  theme_bw()


# Calculate coefficients of determination: easier comparison
coefdet = data.frame(method = c(rep('SVM-pol', 3), rep('SVM-rad', 3), rep('GP', 3)),
           dataset = c('0start', '1adapt', '2rando'),
 coef = c(cor(x = filter(svm.margin, method == 'SVM-pol', cat == 'start')$prob, 
    y = filter(Infer.plt, cat == 'tru')$prob, method = 'pearson')^2,
  cor(x = filter(svm.margin, method == 'SVM-pol', cat == 'adapt')$prob, 
    y = filter(Infer.plt, cat == 'tru')$prob, method = 'pearson')^2,
  cor(x = filter(svm.margin, method == 'SVM-pol', cat == 'rando')$prob, 
    y = filter(Infer.plt, cat == 'tru')$prob, method = 'pearson')^2,
  cor(x = filter(svm.margin, method == 'SVM-rad', cat == 'start')$prob, 
    y = filter(Infer.plt, cat == 'tru')$prob, method = 'pearson')^2,
  cor(x = filter(svm.margin, method == 'SVM-rad', cat == 'adapt')$prob, 
    y = filter(Infer.plt, cat == 'tru')$prob, method = 'pearson')^2,
  cor(x = filter(svm.margin, method == 'SVM-rad', cat == 'rando')$prob, 
    y = filter(Infer.plt, cat == 'tru')$prob, method = 'pearson')^2,
  cor(x = filter(Infer.plt, cat == 'start')$prob, 
    y = c(filter(Infer.plt, cat == 'tru', var == 'x1')$prob, filter(Infer.plt, cat == 'tru', var == 'x2')$prob), 
    method = 'pearson')^2,
  cor(x = filter(Infer.plt, cat == 'adapt')$prob, 
    y = c(filter(Infer.plt, cat == 'tru', var == 'x1')$prob, filter(Infer.plt, cat == 'tru', var == 'x2')$prob), 
    method = 'pearson')^2,
  cor(x = filter(Infer.plt, cat == 'rando')$prob, 
    y = c(filter(Infer.plt, cat == 'tru', var == 'x1')$prob, filter(Infer.plt, cat == 'tru', var == 'x2')$prob), 
    method = 'pearson')^2))

coefdet
write.csv(coefdet, 'Marginals_CoefDet_radan.csv', row.names = F)

ggplot(coefdet) +
  geom_col(mapping = aes(x = dataset, fill = dataset, y = coef))+
  facet_grid(~method) +
  labs(x = '', y = '1-Variable Marginal Coefficient of Determination', subtitle = 'Utopia Distance + Priority') +
  scale_x_discrete(breaks = c()) +
  scale_fill_manual(labels = c('0start' = 'Starting Dataset', '1adapt' = '+ Adaptive Sampling', 
                                 '2rando' = '+ Random Sampling'), name = '',
                    values = c('0start' = 'skyblue2', '1adapt' = 'red', '2rando' = 'green')) +
  scale_y_continuous(expand = expansion(mult = c(0, 0.05)))

Alternative Metric: Accuracy as n samples are collected

SVM.err.n = function(fine.input, input.data){
  mod.list = SVM.mod.all(input.data = input.data)
  err = SVM.err(fine.input = fine.input, mod.list = mod.list)
  # return(err)
  # Additional processing to make it consistent with the other convention
  err.n = data.frame(
    method = err$method,
    err = c(err$rate, err$typ1,     err$typ2),
    # unc = c(err$err,  err$typ1.err, err$typ2.err),
    typ = c(rep('Total', nrow(err)), rep('False Positive', nrow(err)), 
            rep('False Negative', nrow(err)))
  )
  # Place limiter on uncertainty
  # err.n$unc[err.n$unc > err.n$err] = err.n$err[err.n$unc > err.n$err]
  # Lower bound of 1e-8 to avoid NaN values later
  # err.n$unc[err.n$unc < 1e-8] = 1e-8
  err.n$err[err.n$err < 1e-8] = 1e-8
  return(err.n)
}
# Pareto Distance
data.paret = read.csv('../Ex_Quartic/GPar_all_start.csv')
data.delta = read.csv('../Ex_Quartic/GPar_Accept_Delta1.csv')
data.rando = read.csv(file = 'GPar_Random.csv')

# Define acceptance
data.paret$cat = 0; data.delta$cat = 0; data.rando$cat = 0; fine.grid$cat = 0
data.paret$cat[data.paret$dist <= 1] = 1
data.delta$cat[data.delta$dist <= 1] = 1
data.rando$cat[data.rando$dist <= 1] = 1
fine.grid$cat[fine.grid$dist <= 1] = 1

# Loop
nsamp = seq(from = nrow(data.paret)+1, to = nrow(data.delta), by = 1)

# Zero sample state
err0 = SVM.err.n(fine.input = fine.grid, input.data = data.paret)

# Effect of sampling
# err.n = data.frame()
# for(i in nsamp[1:3]){
n.cores <- parallel::detectCores() - 1
my.cluster <- parallel::makeCluster(
  n.cores,
  type = "PSOCK"
  )
doParallel::registerDoParallel(cl = my.cluster)
foreach::getDoParRegistered()
[1] TRUE
err.n = foreach(i = nsamp, .combine = 'rbind') %dopar% {
  # Adaptive
  res = SVM.err.n(fine.input = fine.grid, input.data = data.delta[1:i,])
  # Labels
  res$n = i-nrow(data.paret)
  res$class = 'Pareto Distance'
  res$samp = 'Adaptive'
  # Normalized errors
  res$err.n = res$err/err0$err
  res$unc.min = res$err;     res$unc.max = res$err
  res$unc.min.n = res$err.n; res$unc.max.n = res$err.n
  err.adapt = res
  # res$unc.n = sqrt(res$err.n^2 * ((res$unc/res$err)^2 + (err0$unc/err0$err)^2))
  # err.n = rbind(err.n, res)
  
  # Random samples
  nset = i - nrow(data.paret)
  res.rando = data.frame()
  for(j in 1:30){ 
    # Collect multiple random datasets to get a sense of the variance
    rando = data.rando[c(1:nrow(data.paret), 
                         sample(x = (nrow(data.paret)+1):nrow(data.rando), size = nset)),]
    res = SVM.err.n(fine.input = fine.grid, input.data = rando)
    # Normalized to the starting state
    res$err.n = res$err/err0$err
    res.rando = rbind(res.rando, res)
  }
  err.rando = data.frame()
  # Uncertainty correction based on the variance - in this case want the 95% CI
  for(met in unique(res.rando$method)){
    for(tp in unique(res.rando$typ)){
      sub = dplyr::filter(res.rando, method == met, typ == tp)
      err.rando = rbind(err.rando, data.frame(
          err = median(sub$err),  err.n = median(sub$err.n),
          unc.min = quantile(x = sub$err, probs = 0.025), 
          unc.max = quantile(x = sub$err, probs = 0.975), 
          unc.min.n = quantile(x = sub$err.n, probs = 0.025), 
          unc.max.n = quantile(x = sub$err.n, probs = 0.975), 
          class = 'Pareto Distance', typ = tp, method = met,
          n = i - nrow(data.paret),
          samp = 'Random'))
    }
  }
  rbind(err.adapt, err.rando)
  
  # # Random
  # res = SVM.err.n(fine.input = fine.grid, input.data = data.rando[1:i,])
  # # Labels
  # res$n = i-nrow(data.paret)
  # res$class = 'Pareto Distance'
  # res$samp = 'Random'
  # # Normalized errors
  # res$err.n = res$err/err0$err
  # res$unc.n = sqrt(res$err.n^2 * ((res$unc/res$err)^2 + (err0$unc/err0$err)^2))
  # err.n = rbind(err.n, res)
}
parallel::stopCluster(cl = my.cluster)

# rm(res, rando)
# err.n
# Combine with 0 state
err0$n = 0
err0$class = 'Pareto Distance'
err0$samp = 'Adaptive'
err0$err.n = 1
err0$unc.max   = err0$err; err0$unc.min   = err0$err
err0$unc.max.n = 1;        err0$unc.min.n = 1
err.n = rbind(err0, err.n)
err0$samp = 'Random'
err.n = rbind(err0, err.n)

# Plotting
err.n
ggplot(err.n) +
  geom_errorbar(mapping = aes(x = n, y = err.n, ymin = unc.min.n, ymax = unc.max.n,
                              color = samp)) +
  geom_point(mapping = aes(x = n, y = err.n, color = samp)) +
  facet_grid(typ~method, scale = 'free')
Warning in match(x, table, nomatch = 0L) :
  closing unused connection 6 (<-localhost:11306)
Warning in match(x, table, nomatch = 0L) :
  closing unused connection 5 (<-localhost:11306)
Warning in match(x, table, nomatch = 0L) :
  closing unused connection 4 (<-localhost:11306)

ggplot(err.n) +
  geom_errorbar(mapping = aes(x = n, y = err, ymin = unc.min, ymax = unc.max, color = samp)) +
  geom_point(mapping = aes(x = n, y = err, color = samp)) +
  facet_grid(typ~method, scale = 'free')



# Store data
err.svm = err.n
data.paret = read.csv('../Ex_Quartic/GPar_all_start.csv')
data.cutof = read.csv('../Ex_Quartic/GPar_Accept_Threshold.csv')
data.rando = read.csv(file = 'GPar_Random.csv')

# Define acceptance
data.paret$cat = 0; data.cutof$cat = 0; data.rando$cat = 0; fine.grid$cat = 0
data.paret$cat[data.paret$f1.norm <= 1 & data.paret$f2.norm <= 1] = 1
data.cutof$cat[data.cutof$f1.norm <= 1 & data.cutof$f2.norm <= 1] = 1
data.rando$cat[data.rando$f1.norm <= 1 & data.rando$f2.norm <= 1] = 1
fine.grid$cat[fine.grid$f1.norm <= 1 & fine.grid$f2.norm <= 1] = 1

# Loop
nsamp = seq(from = nrow(data.paret)+1, to = nrow(data.cutof), by = 1)

# Zero sample state
err0 = SVM.err.n(fine.input = fine.grid, input.data = data.paret)

# Effect of sampling
n.cores <- parallel::detectCores() - 1
my.cluster <- parallel::makeCluster(
  n.cores,
  type = "PSOCK"
  )
doParallel::registerDoParallel(cl = my.cluster)
foreach::getDoParRegistered()
[1] TRUE
err.n = foreach(i = nsamp, .combine = 'rbind') %dopar% {
  # Adaptive
  res = SVM.err.n(fine.input = fine.grid, input.data = data.cutof[1:i,])
  # Labels
  res$n = i-nrow(data.paret)
  res$class = 'Threshold Cutoff'
  res$samp = 'Adaptive'
  # Normalized errors
  res$err.n = res$err/err0$err
  res$unc.min = res$err;     res$unc.max = res$err
  res$unc.min.n = res$err.n; res$unc.max.n = res$err.n
  err.adapt = res
  # res$unc.n = sqrt(res$err.n^2 * ((res$unc/res$err)^2 + (err0$unc/err0$err)^2))
  # err.n = rbind(err.n, res)
  
  # Random samples
  nset = i - nrow(data.paret)
  res.rando = data.frame()
  for(j in 1:30){ 
    # Collect multiple random datasets to get a sense of the variance
    rando = data.rando[c(1:nrow(data.paret), 
                         sample(x = (nrow(data.paret)+1):nrow(data.rando), size = nset)),]
    res = SVM.err.n(fine.input = fine.grid, input.data = rando)
    # Normalized to the starting state
    res$err.n = res$err/err0$err
    res.rando = rbind(res.rando, res)
  }
  err.rando = data.frame()
  # Uncertainty correction based on the variance - in this case want the 95% CI
  for(met in unique(res.rando$method)){
    for(tp in unique(res.rando$typ)){
      sub = dplyr::filter(res.rando, method == met, typ == tp)
      err.rando = rbind(err.rando, data.frame(
          err = median(sub$err),  err.n = median(sub$err.n),
          unc.min = quantile(x = sub$err, probs = 0.025), 
          unc.max = quantile(x = sub$err, probs = 0.975), 
          unc.min.n = quantile(x = sub$err.n, probs = 0.025), 
          unc.max.n = quantile(x = sub$err.n, probs = 0.975), 
          class = 'Threshold Cutoff', typ = tp, method = met,
          n = i - nrow(data.paret),
          samp = 'Random'))
    }
  }
  rbind(err.adapt, err.rando)
}
parallel::stopCluster(cl = my.cluster)

# err.n
# Combine with 0 state
err0$n = 0
err0$class = 'Threshold Cutoff'
err0$samp = 'Adaptive'
err0$err.n = 1
err0$unc.max   = err0$err; err0$unc.min   = err0$err
err0$unc.max.n = 1;        err0$unc.min.n = 1
err.n = rbind(err0, err.n)
err0$samp = 'Random'
err.n = rbind(err0, err.n)

# Plotting
# err.n
ggplot(err.n) +
  geom_errorbar(mapping = aes(x = n, y = err.n, ymin = unc.min.n, ymax = unc.max.n,
                              color = samp)) +
  geom_point(mapping = aes(x = n, y = err.n, color = samp)) +
  facet_grid(typ~method, scale = 'free')

ggplot(err.n) +
  geom_errorbar(mapping = aes(x = n, y = err, ymin = unc.min, ymax = unc.max, color = samp)) +
  geom_point(mapping = aes(x = n, y = err, color = samp)) +
  facet_grid(typ~method, scale = 'free')


# Store data
err.svm = rbind(err.svm, err.n)
data.paret = read.csv(file = '../Ex_Quartic/GPar_all_start.csv')
data.radan = read.csv('../Ex_Quartic/GPar_Accept_Radius.csv')
data.rando = read.csv(file = 'GPar_Random.csv')

# Define acceptance
data.paret$cat = 0; data.radan$cat = 0; data.rando$cat = 0; fine.grid$cat = 0
data.paret$cat[sqrt(data.paret$f1.norm^2 + data.paret$f2.norm^2) <= 1 & data.paret$theta > 20] = 1
data.radan$cat[sqrt(data.radan$f1.norm^2 + data.radan$f2.norm^2) <= 1 & data.radan$theta > 20] = 1
data.rando$cat[sqrt(data.rando$f1.norm^2 + data.rando$f2.norm^2) <= 1 & data.rando$theta > 20] = 1
fine.grid$cat[sqrt(fine.grid$f1.norm^2 + fine.grid$f2.norm^2) <= 1 & fine.grid$ang > 20] = 1

# Loop
nsamp = seq(from = nrow(data.paret)+1, to = nrow(data.radan), by = 1)

# Zero sample state
err0 = SVM.err.n(fine.input = fine.grid, input.data = data.paret)

# Effect of sampling
n.cores <- parallel::detectCores() - 1
my.cluster <- parallel::makeCluster(
  n.cores,
  type = "PSOCK"
  )
doParallel::registerDoParallel(cl = my.cluster)
foreach::getDoParRegistered()
[1] TRUE
err.n = foreach(i = nsamp, .combine = 'rbind') %dopar% {
  # Adaptive
  res = SVM.err.n(fine.input = fine.grid, input.data = data.radan[1:i,])
  # Labels
  res$n = i-nrow(data.paret)
  res$class = 'Utopia Distance'
  res$samp = 'Adaptive'
  # Normalized errors
  res$err.n = res$err/err0$err
  res$unc.min = res$err;     res$unc.max = res$err
  res$unc.min.n = res$err.n; res$unc.max.n = res$err.n
  err.adapt = res
  # res$unc.n = sqrt(res$err.n^2 * ((res$unc/res$err)^2 + (err0$unc/err0$err)^2))
  # err.n = rbind(err.n, res)
  
  # Random samples
  nset = i - nrow(data.paret)
  res.rando = data.frame()
  for(j in 1:30){ 
    # Collect multiple random datasets to get a sense of the variance
    rando = data.rando[c(1:nrow(data.paret), 
                         sample(x = (nrow(data.paret)+1):nrow(data.rando), size = nset)),]
    res = SVM.err.n(fine.input = fine.grid, input.data = rando)
    # Normalized to the starting state
    res$err.n = res$err/err0$err
    res.rando = rbind(res.rando, res)
  }
  err.rando = data.frame()
  # Uncertainty correction based on the variance - in this case want the 95% CI
  for(met in unique(res.rando$method)){
    for(tp in unique(res.rando$typ)){
      sub = dplyr::filter(res.rando, method == met, typ == tp)
      err.rando = rbind(err.rando, data.frame(
          err = median(sub$err),  err.n = median(sub$err.n),
          unc.min = quantile(x = sub$err, probs = 0.025), 
          unc.max = quantile(x = sub$err, probs = 0.975), 
          unc.min.n = quantile(x = sub$err.n, probs = 0.025), 
          unc.max.n = quantile(x = sub$err.n, probs = 0.975), 
          class = 'Utopia Distance', typ = tp, method = met,
          n = i - nrow(data.paret),
          samp = 'Random'))
    }
  }
  rbind(err.adapt, err.rando)
}
parallel::stopCluster(cl = my.cluster)
# err.n
# Combine with 0 state
err0$n = 0
err0$class = 'Utopia Distance'
err0$samp = 'Adaptive'
err0$err.n = 1
err0$unc.max   = err0$err; err0$unc.min   = err0$err
err0$unc.max.n = 1;        err0$unc.min.n = 1
err.n = rbind(err0, err.n)
err0$samp = 'Random'
err.n = rbind(err0, err.n)

# Plotting
# err.n
ggplot(err.n) +
  geom_errorbar(mapping = aes(x = n, y = err.n, ymin = unc.min.n, ymax = unc.max.n,
                              color = samp)) +
  geom_point(mapping = aes(x = n, y = err.n, color = samp)) +
  facet_grid(typ~method, scale = 'free')

ggplot(err.n) +
  geom_errorbar(mapping = aes(x = n, y = err, ymin = unc.min, ymax = unc.max, color = samp)) +
  geom_point(mapping = aes(x = n, y = err, color = samp)) +
  facet_grid(typ~method, scale = 'free')


# Store data
err.svm = rbind(err.svm, err.n)

write.csv(err.svm, 'SVM_Error-Nsamp.csv', row.names = F)
import.marginal = function(svm.margin, typ){
  # Set up output
  import.svm.margin = data.frame()
  # Loop across methods
  for(met in unique(svm.margin$method)){
    sub = filter(svm.margin, cat == 'adapt', var == 'x1', method == met)
    import.svm.margin = rbind(import.svm.margin, data.frame(
      import = diff(range(sub$prob))/sum(sub$psd),
      var = 'x1',
      method = paste('Marginal', substring(met, 4), sep = ''),
      sd = sqrt((max(sub$prob)*(1-max(sub$prob)) + min(sub$prob)*(1-min(sub$prob)) * 
                    diff(range(sub$prob)) + var(sub$psd))/nrow(sub))
      ))
    sub = filter(svm.margin, cat == 'adapt', var == 'x2', method == met)
    import.svm.margin = rbind(import.svm.margin, data.frame(
      import = diff(range(sub$prob))/sum(sub$psd),
      var = 'x2',
      method = paste('Marginal', substring(met, 4), sep = ''),
      sd = sqrt((max(sub$prob)*(1-max(sub$prob)) + min(sub$prob)*(1-min(sub$prob)) * 
                    diff(range(sub$prob)) + var(sub$psd))/nrow(sub))
      ))
  }
  # Type, relative importance
  import.svm.margin$typ = typ
  import.svm.margin$r.import = c(import.svm.margin$import[1:2]/max(import.svm.margin$import[1:2]),
                                 import.svm.margin$import[3:4]/max(import.svm.margin$import[3:4]))
  return(import.svm.margin)
}

svm.import.margin = rbind(import.marginal(svm.margin = read.csv('Margin_SVM_dist.csv'), typ = 'Pareto Distance'), 
      import.marginal(svm.margin = read.csv('Margin_SVM_cutof.csv'), typ = 'Threshold Cutoff'), 
      import.marginal(svm.margin = read.csv('Margin_SVM_radan.csv'), typ = 'Utopia Distance'))
import.rank = read.csv('../Ex_Quartic/Importance.csv')
import.rank = import.rank[, !names(import.rank) %in% c('X')]
import.rank$method = unlist(lapply(import.rank$method, function(x) paste(x, '-GP', sep = '')))
import.rank
# import.rank$method[import.rank$method == 'Shapley'] = 'Shapley-GP'

SVM.shap.delta = SVM.shap(mod = mod.adapt.delta)
SVM.shap.delta$typ = 'Pareto Distance'
SVM.shap.cutof = SVM.shap(mod = mod.adapt.cutof)
SVM.shap.cutof$typ = 'Threshold Cutoff'
SVM.shap.radan = SVM.shap(mod = mod.adapt.radan)
SVM.shap.radan$typ = 'Utopia Distance'


ggplot(rbind(import.rank, SVM.shap.delta, SVM.shap.cutof, SVM.shap.radan, svm.import.margin)) +
  geom_col(mapping = aes(x = var, y = abs(r.import), fill = var)) +
  facet_grid(method~typ, scales = 'free_y') +
  labs(x = '', y = 'Relative Importance') +
  guides(fill = FALSE) + scale_x_discrete(labels = c('x1' = expression('x'[1]), 'x2' = expression('x'[2])))
`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.

ggplot(rbind(import.rank, SVM.shap.delta, SVM.shap.cutof, SVM.shap.radan, svm.import.margin)) +
  geom_col(mapping = aes(x = var, y = import, fill = var)) +
  facet_grid(method~typ, scales = 'free_y') +
  labs(x = '', y = 'Relative Importance') +
  guides(fill = FALSE) + scale_x_discrete(labels = c('x1' = expression('x'[1]), 'x2' = expression('x'[2])))
`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.

# Rows: Selection method = Pareto distance, Objective Cutoff, Utopia Distance + Priority
# Columns: method = Shapley with SVM, Shapley with GP, new method
# Split up by model method
import.all = rbind(import.rank, SVM.shap.delta, SVM.shap.cutof, SVM.shap.radan, svm.import.margin)
# Classification split
split = unlist(lapply(import.all$method, strsplit, '-'))
import.all$method = split[c(TRUE, FALSE)]
import.all$model = split[c(FALSE, TRUE)]

import.all
ggplot(import.all) +
  geom_col(mapping = aes(x = model, y = import, fill = var), position = 'dodge') +
  geom_errorbar(mapping = aes(x = model, ymax = import + sd, ymin = import - sd, group = var), 
                position = 'dodge', width = 0.9) +
  facet_grid(method~typ, scales = 'free_y') +
  labs(x = 'Model', y = 'Relative Importance (Unitless)') +
  scale_x_discrete(labels = c('GP', 'pol' = 'SVM-pol', 'rad' = 'SVM-rad')) +
  scale_fill_discrete(name = '', labels = c('x1' = expression('x'[1]), 'x2' = expression('x'[2])))


# Relative importance (normalized): uncertainty is divided by the same value
r.max = import.all$import
for(i in seq(from = 1, to = length(r.max)-1, by = 2)){
  r.max[c(i,i+1)] = max(abs(r.max[c(i,i+1)]))
}
import.all$r.import = abs(import.all$import)/r.max
import.all$r.sd = import.all$sd / r.max
ggplot(import.all) +
  geom_col(mapping = aes(x = model, y = r.import, fill = var), position = 'dodge') +
  geom_errorbar(mapping = aes(x = model, ymax = r.import + r.sd, ymin = r.import - r.sd, group = var), 
                position = 'dodge', width = 0.9) +
  facet_grid(method~typ, scales = 'free_y') +
  labs(x = 'Model', y = 'Relative Importance (Unitless)') +
  scale_x_discrete(labels = c('GP', 'pol' = 'SVM-pol', 'rad' = 'SVM-rad')) +
  scale_fill_discrete(name = '', labels = c('x1' = expression('x'[1]), 'x2' = expression('x'[2])))


rm(r.max)

write.csv(import.all, 'ImportRank_Method.csv', row.names = F)

It is clear that the importance metric that was developed here provided more consistent results for the global importance than Shapley values. The importance among different ML models gives slightly different results, suggesting differences in the models more than problems with the importance metric.

Existing Method: Gaussian Mixtures

Since the GM models are unsupervised, there cannot be control for what the selection criteria are. A superficial analysis will be conducted to check what the ML algorithm is converging to, but the results will be interpreted solely in terms of descriptive statistics.

For the purposes of analysis, three models will be tested based on the input to the ML algorithm: * (x1, x2, f1, f2): the full set of inputs and outputs - since it has the most data, this will likely be the most robust * (x1, x2): negative control of just the inputs - it should group the points based on the sampling densities * (f1, f2): outputs only - this should have the best distinction between good and bad performing groups

Up to 12 categories will be allowed, and any type of Gaussian models will be accepted. This will lead to longer fitting time, but should help with accuracy.

GPar.all = read.csv(file = '../Ex_Quartic/GPar_all_start.csv')

# names(GPar.all)
max.cat = 12
cluster.all = Mclust(data = GPar.all[,c('x1', 'x2', 'f1', 'f2')], G = 2:max.cat)
cluster.in = Mclust(data = GPar.all[,c('x1', 'x2')], G = 2:max.cat)
cluster.out = Mclust(data = GPar.all[,c('f1', 'f2')], G = 2:max.cat)
# summary(cluster, parameters = TRUE)

GPar.all$typ.all = cluster.all$classification
GPar.all$typ.in = cluster.in$classification
GPar.all$typ.out = cluster.out$classification

ggplot() +
  # Boundaries: +/- some separation from 0.5
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = dist, color = 'delta'), breaks = c(1)) +
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = cutof, color = 'cutof'), breaks = c(0.5)) +
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = rad, color = 'rad'), breaks = c(1)) +
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = ang, color = 'ang'), breaks = c(50)) +
  # Pareto frontier
  geom_smooth(data = GPar.front, mapping = aes(x = x1, y = x2, color = 'Pareto'), 
              level = 0.95, formula = (y~x), method = 'loess') + 
  # Clustering
  geom_point(data = GPar.all, mapping = aes(x = x1, y = x2, fill = as.factor(typ.all)), size = 2.5, shape = 21) +

  labs(x = expression('x'[1]), y = expression('x'[2]), 
       color = 'Acceptance Criteria', fill = 'Group', 
       subtitle = '(x1, x2, f1, f2)') +
  scale_color_manual(values = c('delta' = '#1b9e77', 'cutof' = '#d95f02', 
                                'rad' = '#7570b3', 'ang' = '#e7298a', 
                                'Pareto' = 'black'),
                     labels = c('delta' = expression(delta*' < 1'), 
                                'cutof' = expression('F'[1]^'*'*'< 1, F'[2]^'*'*'< 1'), 
                                'rad' = expression('r < 1'),
                                'ang' = expression('F'[1]*'and F'[2]*' Balance'),
                                'Pareto' = expression('Pareto Front')),
                     breaks = c('delta', 'cutof', 'rad', 'ang', 'Pareto')) +
  theme_classic() + #theme(legend.position = c(0.85, 0.75)) + 
  scale_x_continuous(expand = c(0, 0), limits = c(0, 5)) + scale_y_continuous(expand = c(0, 0), limits = c(0, 5)) +
  guides(colour = guide_legend(override.aes = list(fill = alpha('white', 1))))


ggplot() +
  # Boundaries: +/- some separation from 0.5
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = dist, color = 'delta'), breaks = c(1)) +
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = cutof, color = 'cutof'), breaks = c(0.5)) +
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = rad, color = 'rad'), breaks = c(1)) +
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = ang, color = 'ang'), breaks = c(50)) +
  # Pareto frontier
  geom_smooth(data = GPar.front, mapping = aes(x = x1, y = x2, color = 'Pareto'), 
              level = 0.95, formula = (y~x), method = 'loess') + 
  # Clustering
  geom_point(data = GPar.all, mapping = aes(x = x1, y = x2, fill = as.factor(typ.in)), size = 2.5, shape = 21) +

  labs(x = expression('x'[1]), y = expression('x'[2]), 
       color = 'Acceptance Criteria', fill = 'Group', 
       subtitle = '(x1, x2)') +
  scale_color_manual(values = c('delta' = '#1b9e77', 'cutof' = '#d95f02', 
                                'rad' = '#7570b3', 'ang' = '#e7298a', 
                                'Pareto' = 'black'),
                     labels = c('delta' = expression(delta*' < 1'), 
                                'cutof' = expression('F'[1]^'*'*'< 1, F'[2]^'*'*'< 1'), 
                                'rad' = expression('r < 1'),
                                'ang' = expression('F'[1]*'and F'[2]*' Balance'),
                                'Pareto' = expression('Pareto Front')),
                     breaks = c('delta', 'cutof', 'rad', 'ang', 'Pareto')) +
  theme_classic() + #theme(legend.position = c(0.85, 0.75)) + 
  scale_x_continuous(expand = c(0, 0), limits = c(0, 5)) + scale_y_continuous(expand = c(0, 0), limits = c(0, 5)) +
  guides(colour = guide_legend(override.aes = list(fill = alpha('white', 1))))


ggplot() +
  # Boundaries: +/- some separation from 0.5
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = dist, color = 'delta'), breaks = c(1)) +
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = cutof, color = 'cutof'), breaks = c(0.5)) +
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = rad, color = 'rad'), breaks = c(1)) +
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = ang, color = 'ang'), breaks = c(50)) +
  # Pareto frontier
  geom_smooth(data = GPar.front, mapping = aes(x = x1, y = x2, color = 'Pareto'), 
              level = 0.95, formula = (y~x), method = 'loess') + 
  # Clustering
  geom_point(data = GPar.all, mapping = aes(x = x1, y = x2, fill = as.factor(typ.out)), size = 2.5, shape = 21) +

  labs(x = expression('x'[1]), y = expression('x'[2]), 
       color = 'Acceptance Criteria', fill = 'Group', 
       subtitle = '(f1, f2)') +
  scale_color_manual(values = c('delta' = '#1b9e77', 'cutof' = '#d95f02', 
                                'rad' = '#7570b3', 'ang' = '#e7298a', 
                                'Pareto' = 'black'),
                     labels = c('delta' = expression(delta*' < 1'), 
                                'cutof' = expression('F'[1]^'*'*'< 1, F'[2]^'*'*'< 1'), 
                                'rad' = expression('r < 1'),
                                'ang' = expression('F'[1]*'and F'[2]*' Balance'),
                                'Pareto' = expression('Pareto Front')),
                     breaks = c('delta', 'cutof', 'rad', 'ang', 'Pareto')) +
  theme_classic() + #theme(legend.position = c(0.85, 0.75)) + 
  scale_x_continuous(expand = c(0, 0), limits = c(0, 5)) + scale_y_continuous(expand = c(0, 0), limits = c(0, 5)) +
  guides(colour = guide_legend(override.aes = list(fill = alpha('white', 1))))

The complete input (x1, x2, f1, f2) overcomplicates the system, providing 8 groups. One of these groups is clearly the Pareto frontier, but it does not include any points that are of similar performance. There are points to either side of it suggesting similar performance, but how far they extend is difficult to interpret.

The negative control (x1, x2) gives the expected result of largely grouping based on sampling density. This means the highly sampled Pareto frontier and local minimum are their own groups, and the rest of the space is divided based around the boundary between the highly sampled regions.

The output-only model gives the most useful results, as it groups the Pareto frontier inside of another high-performing group, which also includes the local optimum and the space spanning to it. It roughly lines up with the Pareto distance or threshold criteria; it does not appear to prioritize the angle or utopia distance. Showing only this model at finer resolution as it is the only relevant performing model

res = predict(object = cluster.out, newdata = fine.grid[c('f1', 'f2')])
fine.grid$cluster.out = res$classification
ggplot() +
  # Cluster results
  geom_point(data = fine.grid, mapping = aes(x = x1, y = x2, color = as.factor(cluster.out))) +
  # Boundaries: +/- some separation from 0.5
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = dist), color = 'black', breaks = c(1)) +
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = cutof), color = 'red', breaks = c(0.5)) +

  labs(x = expression('x'[1]), y = expression('x'[2]), 
       color = 'Group', fill = 'Group', 
       subtitle = '(f1, f2)') +
  theme_classic() + #theme(legend.position = c(0.85, 0.75)) + 
  scale_x_continuous(expand = c(0, 0), limits = c(0, 5)) + scale_y_continuous(expand = c(0, 0), limits = c(0, 5)) +
  guides(colour = guide_legend(override.aes = list(fill = alpha('white', 1))))


res = predict(object = cluster.all, newdata = fine.grid[c('x1', 'x2', 'f1', 'f2')])
fine.grid$cluster.all = res$classification
ggplot() +
  # Cluster results
  geom_point(data = fine.grid, mapping = aes(x = x1, y = x2, color = as.factor(cluster.all)), size = 4) +
  # Boundaries: +/- some separation from 0.5
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = dist), color = 'black', breaks = c(1)) +
  geom_contour(data = fine.grid, mapping = aes(x = x1, y = x2, z = cutof), color = 'red', breaks = c(0.5)) +

  labs(x = expression('x'[1]), y = expression('x'[2]), 
       color = 'Group', fill = 'Group', 
       subtitle = '(x1, x2, f1, f2)') +
  theme_classic() + #theme(legend.position = c(0.85, 0.75)) + 
  scale_x_continuous(expand = c(0, 0), limits = c(0, 5)) + scale_y_continuous(expand = c(0, 0), limits = c(0, 5)) +
  guides(colour = guide_legend(override.aes = list(fill = alpha('white', 1))))


ggplot() +
  # Cluster results
  geom_point(data = filter(fine.grid, f2 < 50, f1 < 200), mapping = aes(x = f1, y = f2, color = as.factor(cluster.all)), size = 2) +
  labs(x = expression('f'[1]), y = expression('f'[2]), 
       color = 'Group', fill = 'Group', 
       subtitle = '(x1, x2, f1, f2)') +
  theme_classic() +
  guides(colour = guide_legend(override.aes = list(fill = alpha('white', 1))))

The (f1, f2) model appears to give a cluster that is somewhere between a threshold cutoff and the Pareto distance cutoff. In contrast, the behavior of the (x1, x2, f1, f2) model provides many more groups, including splitting the Pareto front into about three different categories with no obvious analog to why the boundaries are where they are. It appears to roughly fit the same boundaries of Pareto distance or threshold cutoffs, but not very well. A rough approximation is that groups (5, 6) are the Pareto front, groups (2, 3, 7) make up the region close to the Pareto front, and (1, 4, 8) are the region far from the front.

The model for (f1, f2) is going to give the same conditional probabilities as the Pareto distance or threshold acceptance criteria based on this similarity in the shape of the boundary. The interesting result to interpret is the (x1, x2, f1, f2), particularly when marginalizing to (x1, x2). Assuming that calculating (f1, f2) is expensive, the best approximation is that found from the GP models used to find the Pareto front itself. These will give (f1, f2) as a bivariate Gaussian distribution, which can be sampled from to estimate the likelihood that it falls into the Pareto front, the region close to it, or the region far from it. This is achievable with a sequential Monte Carlo: given x1, sample x2 from the range, find the distribution of (f1, f2), sample (f1, f2), and solve the classification into the three groups.

f1.mod = fill.sample.mod(GPar.data = GPar.all, input.name = c('x1', 'x2'), output.name = 'f1')
f2.mod = fill.sample.mod(GPar.data = GPar.all, input.name = c('x1', 'x2'), output.name = 'f2')

x.rng = seq(from = 0, to = 5, length.out = 50)
nsamp.x = 100; nsamp.f = 100
gm.margin = data.frame()
for(x in x.rng){
  # x1
  # Sample x2
  inframe = data.frame(x1 = x, x2 = runif(n = nsamp.x, min = 0, max = 5))
  classes = c()
  for(n in 1:nrow(inframe)){
    # Estimate distribution for f1, f2
    f1.est = predict(object = f1.mod, newdata = inframe, type = 'UK')
    f2.est = predict(object = f2.mod, newdata = inframe, type = 'UK')
    test.frame = data.frame(x1 = inframe$x1, x2 = inframe$x2,
                            f1 = rnorm(n = nsamp.f, mean = f1.est$mean, sd = f1.est$sd),
                            f2 = rnorm(n = nsamp.f, mean = f2.est$mean, sd = f2.est$sd))
    res = predict(object = cluster.all, newdata = test.frame)
    classes = c(classes, res$classification)
  }
  classes = data.frame(group = classes)
  gm.margin = rbind(gm.margin, data.frame(x = x, var = 'x1', 
                         p.pare = nrow(filter(classes, group == 5 | group == 6))/(nsamp.f*nsamp.x), 
                         p.near = nrow(filter(classes, group == 2 | group == 3 | group == 7))/(nsamp.f*nsamp.x), 
                         p.dist = nrow(filter(classes, group == 1 | group == 4 | group == 8))/(nsamp.f*nsamp.x)))
  # x2
  # Sample x2
  inframe = data.frame(x2 = x, x1 = runif(n = nsamp.x, min = 0, max = 5))
  classes = c()
  for(n in 1:nrow(inframe)){
    # Estimate distribution for f1, f2
    f1.est = predict(object = f1.mod, newdata = inframe, type = 'UK')
    f2.est = predict(object = f2.mod, newdata = inframe, type = 'UK')
    test.frame = data.frame(x1 = inframe$x1, x2 = inframe$x2,
                            f1 = rnorm(n = nsamp.f, mean = f1.est$mean, sd = f1.est$sd),
                            f2 = rnorm(n = nsamp.f, mean = f2.est$mean, sd = f2.est$sd))
    res = predict(object = cluster.all, newdata = test.frame)
    classes = c(classes, res$classification)
  }
  classes = data.frame(group = classes)
  gm.margin = rbind(gm.margin, data.frame(x = x, var = 'x2', 
                         p.pare = nrow(filter(classes, group == 5 | group == 6))/(nsamp.f*nsamp.x), 
                         p.near = nrow(filter(classes, group == 2 | group == 3 | group == 7))/(nsamp.f*nsamp.x), 
                         p.dist = nrow(filter(classes, group == 1 | group == 4 | group == 8))/(nsamp.f*nsamp.x)))
}

write.csv(gm.margin, 'Margin_GM.csv', row.names = F)
gm.margin = read.csv('Margin_GM.csv')
gm.margin = data.frame(x = rep(gm.margin$x, 3),
                       var = rep(gm.margin$var, 3),
                       prob = c(gm.margin$p.pare, gm.margin$p.near, gm.margin$p.dist),
                       typ = c(rep('Pareto', nrow(gm.margin)), 
                               rep('Near-Pareto', nrow(gm.margin)), 
                               rep('Suboptimal', nrow(gm.margin))))
ggplot(gm.margin) +
  geom_path(mapping = aes(x = x, y = prob, color = typ)) +
  facet_wrap(~var, nrow = 2) +
  scale_color_brewer(palette = 'Dark2') + 
  labs(x = 'Input Value', y = 'Conditional Probability', color = 'Region')


gm.margin = read.csv('Margin_GM.csv')
gm.margin = data.frame(x = rep(gm.margin$x),
                       var = rep(gm.margin$var),
                       prob = gm.margin$p.pare + gm.margin$p.near,
                       typ = 'Optimal')
ggplot(gm.margin) +
  geom_path(mapping = aes(x = x, y = prob, color = typ)) +
  facet_wrap(~var, nrow = 2) +
  scale_color_brewer(palette = 'Dark2') + 
  labs(x = 'Input Value', y = 'Conditional Probability', color = 'Region')

The estimated probability of being optimal (Pareto front group or the near-Pareto group) is similar to that calculated through the other methods for the Pareto distance or threshold criteria. However, because of the numerous variables required to generate the model, a larger and more complicated sampling procedure is necessary for accurate estimates.

Overall:

LS0tCnRpdGxlOiAiUGF0dGVybiBSZWNvZ25pdGlvbiBNZXRob2QgQ29tcGFyaXNvbjogUXVhcnRpYyBQb2x5bm9taWFsIgphdXRob3I6ICJKb25hdGhhbiBCb3VhbGF2b25nIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgo8IS0tIG91dHB1dDogICAgLS0+CjwhLS0gICBtZF9kb2N1bWVudDogLS0+CjwhLS0gICAgIHZhcmlhbnQ6IG1hcmtkb3duX2dpdGh1YiAtLT4KCiMgRGVzY3JpcHRpb24KVGhpcyBub3RlYm9vayB0YWtlcyB0aGUgZGF0YSBnZW5lcmF0ZWQgYnkgdGhlIHNjcmlwdCBpbiAvRXhfUXVhcnRpYywgd2hpY2ggZGVzY3JpYmVzIHRoZSBuZXdseSBkZXZlbG9wZWQgYWRhcHRpdmUgc2FtcGxpbmcgYW5kIGNsYXNzaWZpY2F0aW9uIG1ldGhvZCwgYW5kIGNvbXBhcmVzIGl0IGV4aXN0aW5nIGNsYXNzaWZpY2F0aW9uIGFsZ29yaXRobXMuIAoKVGhlIGNsYXNzaWZpY2F0aW9uIGFsZ29yaXRobXMgYXJlOgoqIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmVzOiBleGFtcGxlIHN1cGVydmlzZWQgbGVhcm5pbmcgcHJvYmxlbQoqIEdhdXNzaWFuIG1peHR1cmUgbW9kZWxzOiBleGFtcGxlIHVuc3VwZXJ2aXNlZCBsZWFybmluZyBwcm9ibGVtCgpGb3IgZWFzZSBvZiBjYWxjdWxhdGlvbiwgdGhlIGl0ZXJhdGl2ZSByZWZpbmVtZW50IG9mIHRoZSBHUCBtb2RlbHMgYXJlIG5vdCBpbmNsdWRlZCBoZXJlLCBub3IgYXJlIHRoZSBjYWxjdWxhdGlvbnMgb2YgdGhlIG1hcmdpbmFsaXplZCBwcm9iYWJpbGl0aWVzLgpUaGUgU1ZNIGFuZCBHTU0gbWV0aG9kcyBhcmUgdGVzdGVkIHdpdGggKDEpIHRoZSBkYXRhIGFmdGVyIGZpbmRpbmcgdGhlIFBhcmV0byBmcm9udCAoYmVmb3JlIHRoZSBuZXcgYWRhcHRpdmUgc2FtcGxpbmcgcHJvY2VzcyksICgyKSB0aGUgZGF0YSBhZnRlciBhZGFwdGl2ZSBzYW1wbGluZyByZWZpbmVtZW50LCBhbmQgKDMpIHRoZSBkYXRhIGFmdGVyIHRoZSBQYXJldG8gZnJvbnQgc2VhcmNoIHdpdGggYWRkaXRpb25hbCByYW5kb20gc2FtcGxpbmcgdG8gc2VlIGlmIHRoZSBuZXcgc2FtcGxpbmcgbWV0aG9kIGlzIHVzZWZ1bCBmb3IgdGhlc2Ugb3RoZXIgcHJvY2Vzc2VzIGFzIHdlbGwuCgpDb21wYXJpc29uIGFtb25nIHRoZSBzdXBlcnZpc2VkIG1ldGhvZHMgaXMgZG9uZSBieSBsb29raW5nIGF0IHRoZSBlcnJvciByYXRlIGFjcm9zcyB0aGUgZW50aXJlIHNwYWNlLiAKU2luY2UgdGhlIHRlc3QgZnVuY3Rpb24gaXMgYSBzaW1wbGUgcG9seW5vbWlhbCwgdGhlIHNvbHV0aW9uIG9mIHdoYXQgaXMgYWNjZXB0YWJsZSBjYW4gYmUgZm91bmQgZXhwbGljaXRseS4KU2luY2UgdGhlIEdQIG1ldGhvZCBwcm9kdWNlcyBhIGZ1enp5IGNsYXNzaWZpZXIsIHRoZSBlcnJvciByYXRlIGlzIHRoZSBhdmVyYWdlIG1pc2NsYXNzaWZpY2F0aW9uIHByb2JhYmlsaXR5LgpGb3IgdGhlIFNWTSBtZXRob2QsIHRoZSBlcnJvciByYXRlIGlzIHNpbXBseSB0aGUgbnVtYmVyIG9mIGluY29ycmVjdGx5IGNhdGVnb3JpemVkIHBvaW50cyBkaXZpZGVkIGJ5IHRoZSB0b3RhbCBudW1iZXIgb2YgdGVzdCBwb2ludHMuCgpJbiBhZGRpdGlvbiB0byB0aGUgYWNjdXJhY3kgY29tcGFyaXNvbiwgYSBjb21wYXJpc29uIG9mIHRoZSBpbXBvcnRhbmNlIHJhbmtpbmcgbWV0cmljIGRldmVsb3BlZCBmb3IgdXNpbmcgR1AgaW4gY2xhc3NpZmljYXRpb24gcHJvYmxlbXMgd2lsbCBiZSBjb21wYXJlZCB0byBTaGFwbGV5IHZhbHVlcy4KQXMgd2l0aCB0aGUgYWNjdXJhY3kgY29tcGFyaXNvbiwgdGhpcyB3aWxsIGJlIHRlc3RlZCBhZ2FpbnN0IHRoZSBkYXRhIGJlZm9yZSByZWZpbmVtZW50LCBhZnRlciByZWZpbmVtZW50LCBvciBhZnRlciBhIHJhbmRvbSBzYW1wbGUuCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpgYGAKCkNsZWFyIHRoZSB3b3Jrc3BhY2UgYW5kIGRlZmluZSB0aGUgZnVuY3Rpb25zLgoKYGBge3IgTG9hZCBQYWNrYWdlc30KIyBTZXR1cApybShsaXN0ID0gbHMoKSkKIyBWaXN1YWxpemF0aW9uCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShwYXRjaHdvcmspCiMgUGFyYWxsZWwgcHJvY2Vzc2luZwpsaWJyYXJ5KHBhcmFsbGVsKQpsaWJyYXJ5KGRvUGFyYWxsZWwpCiMgR2F1c3NpYW4gcHJvY2Vzc2VzCmxpYnJhcnkoR1BhcmV0bykKbGlicmFyeShEaWNlS3JpZ2luZykKbGlicmFyeShEaWNlT3B0aW0pCiMgT3B0aW1pemF0aW9uCmxpYnJhcnkoR0EpCiMgR2F1c3NpYW4gTWl4dHVyZSBNb2RlbHMKbGlicmFyeShtY2x1c3QpCiMgU3VwcG9ydCBWZWN0b3IgTWFjaGluZXMKbGlicmFyeShlMTA3MSkKYGBgCgojIFJlbGV2YW50IGZ1bmN0aW9ucwoKVGhlIHF1YXJ0aWMgb2JqZWN0aXZlIGZ1bmN0aW9ucwoKYGBge3IgRnVuY3Rpb25zfQojIFNldCAoeDEsIHgyKSBvbiByYW5nZSBvZiBbMCwgNV0KCiMgT2JqZWN0aXZlIGZ1bmN0aW9ucyBhcmUgYmFzZWQgb24gdGhlIHF1YWRyYXRpYyBmdW5jdGlvbnMgdXNlZCBwcmV2aW91c2x5LCBidXQgd2l0aCBhbiBhZGRpdGlvbmFsIGxvY2FsIG1pbmltdW0KZjEgPSBmdW5jdGlvbih4MSwgeDIpewogIHJldHVybigyMCooeDEgLSAwLjc1KV4yICsgMTkwICsgMTEuNTgqeDJeNCAtIDExNS44NSp4Ml4zICsgMzgzLjEzKngyXjIgLSA0NjMuNTAqeDIpCn0KIyBUaGUgc2Vjb25kIG9iamVjdGl2ZSBmdW5jdGlvbiBpcyBhbHNvIHBhcnRpYWxseSByb3RhdGVkIHNvIHRoZSBsb2NhbCBvcHRpbXVtIGlzIG5vdCBwZXJmZWN0bHkgYWxpZ25lZApmMiA9IGZ1bmN0aW9uKHgxLCB4Mil7CiAgIyBSZW1hcCBib3RoIHZhcmlhYmxlczogcm90YXRlIDMwIGRlZ3JlZXMgY291bnRlcmNsb2Nrd2lzZQogIGFuZyA9IC1waS8yNAogIG4xID0geDEqY29zKGFuZykgLSB4MipzaW4oYW5nKQogIG4yID0geDEqc2luKGFuZykgKyB4Mipjb3MoYW5nKQogICMgcmV0dXJuKCh4MSAtIDIuNSleMiArIDgwICsgMS43NzgqeDJeNCAtIDIwKngyXjMgKyA3OC41NzMqeDJeMiAtIDEyNC42NjQqeDIpCiAgcmV0dXJuKChuMSAtIDIuNSleMiArIDgwICsgMS43NzgqbjJeNCAtIDIwKm4yXjMgKyA3OC41NzMqbjJeMiAtIDEyNC42NjQqbjIpCn0KCgojIFVzaW5nIEdQYXJldG86IE5lZWQgaW5wdXRzIGFuZCBvdXRwdXRzIGFzIHZlY3RvcnMvbWF0cmljZXMgbm90IGFzIGRhdGFmcmFtZXMuIENvYXJzZSBpbml0aWFsIGRlc2lnbgpmdW4gPSBmdW5jdGlvbih4KXsKICB4MSA9IHhbMV07IHgyID0geFsyXQogIHJldHVybihjKGYxKHgxLCB4MiksIGYyKHgxLCB4MikpKQp9CgpgYGAKCk5vcm1hbGl6YXRpb24tcmVsYXRlZCBmdW5jdGlvbnMKYGBge3IgRnVuY3Rpb25zIHBhcnQgMn0KIyBOb3JtYWxpemVkIG9iamVjdGl2ZSBmdW5jdGlvbnMKbi5vYmogPSBmdW5jdGlvbihHUGFyLmRhdGEsIEdQYXIuZnJvbnQpewogICMgR2l2ZW4gZGF0YWZyYW1lcyB0aGF0IGRlc2NyaWJlIHRoZSBlbnRpcmUgZGF0YXNldCBhbmQgdGhlIGZyb250LCBmaW5kIHRoZSBub3JtYWxpemVkICh4LHkpCiAgIyBPYmplY3RpdmUgZnVuY3Rpb25zIGFyZSBuYW1lZCAnZjEnIGFuZCAnZjInCiAgCiAgIyBOb3JtYWxpemUgdGhlIG9iamVjdGl2ZSBvdXRwdXRzIHNvIHRoYXQgdGhlIHV0b3BpYSBwb2ludCBpcyAoMCwwKSBhbmQgdGhlIG5hZGlyIHBvaW50IGlzICgxLDEpCiAgZjEudXAgPSBHUGFyLmZyb250JGYxW3doaWNoLm1pbihHUGFyLmZyb250JGYyKV0KICBmMi51cCA9IEdQYXIuZnJvbnQkZjJbd2hpY2gubWluKEdQYXIuZnJvbnQkZjEpXQogIEdQYXIuZGF0YSRmMS5ub3JtID0gKEdQYXIuZGF0YSRmMSAtIG1pbihHUGFyLmZyb250JGYxKSkvKGYxLnVwIC0gbWluKEdQYXIuZnJvbnQkZjEpKQogIEdQYXIuZGF0YSRmMi5ub3JtID0gKEdQYXIuZGF0YSRmMiAtIG1pbihHUGFyLmZyb250JGYyKSkvKGYyLnVwIC0gbWluKEdQYXIuZnJvbnQkZjIpKQoKICByZXR1cm4oR1Bhci5kYXRhKQp9CgojIENhbGN1bGF0ZSB0aGUgbm9ybWFsaXplZCBkaXN0YW5jZQpuLmRpc3QgPSBmdW5jdGlvbihmMS5ub3JtLCBmMi5ub3JtLCBHUGFyLmZyb250KXsKICAjIEdpdmVuIHRoZSBub3JtYWxpemVkIGNvb3JkaW5hdGVzIChmMS5ub3JtLCBmMi5ub3JtKSBhbmQgdGhlIFBhcmV0byBmcm9udGllciBlc3RpbWF0ZSwKICAjIGZpbmQgdGhlIGRpc3RhbmNlIGFsb25nIHRoZSBjb25zdGFudCBmMi9mMSByYXRpbyBsaW5lCiAgCiAgIyBEZXRlcm1pbmUgdGhlIHR3byBwb2ludHMgb24gdGhlIFBhcmV0byBmcm9udCB0aGF0IGRlZmluZSB0aGUgcmVsZXZhbnQgc2VnbWVudAogIEdQYXIuZnJvbnQkdGhldGEgPSBhdGFuKEdQYXIuZnJvbnQkZjIubm9ybSAvIEdQYXIuZnJvbnQkZjEubm9ybSkKICBpZihmMS5ub3JtIDwgMCl7ZjEubm9ybSA9IDB9CiAgaWYoZjIubm9ybSA8IDApe2YyLm5vcm0gPSAwfQogIHJhdGlvID0gYXRhbihmMi5ub3JtL2YxLm5vcm0pCiAgCiAgIyBDaGVjayBpZiB0aGUgYW5nbGUgaXMgdGhlIHNhbWUgYXMgYSBwb2ludCBvbiB0aGUgUGFyZXRvIGZyb250CiAgaWYoYW55KGFicyhyYXRpbyAtIEdQYXIuZnJvbnQkdGhldGEpIDwgMWUtNSkpewogICAgcG9zID0gd2hpY2gubWluKGFicyhyYXRpbyAtIEdQYXIuZnJvbnQkdGhldGEpKQogICAgUGFyLnggPSBHUGFyLmZyb250JGYxLm5vcm1bcG9zXQogICAgUGFyLnkgPSBHUGFyLmZyb250JGYyLm5vcm1bcG9zXQogIH0gZWxzZXsgIyBPdGhlcndpc2UsIHR3byBwb2ludHMgYXJlIG5lZWRlZCBmb3IgbGluZWFyIGludGVycG9sYXRpb24KICAgICMgQnJlYWsgdGhlIGRhdGFmcmFtZSBpbnRvIHRoZXRhIGFib3ZlIGFuZCBiZWxvdwogICAgUGFyLmFib3ZlID0gR1Bhci5mcm9udFtHUGFyLmZyb250JHRoZXRhIC0gcmF0aW8gPiAwLF0KICAgIFBhci5iZWxvdyA9IEdQYXIuZnJvbnRbR1Bhci5mcm9udCR0aGV0YSAtIHJhdGlvIDwgMCxdCiAgICAjIEZpbmQgdGhlIHBvaW50IGNsb3Nlc3QgdG8gdGhlIGFuZ2xlCiAgICBwb3MuYWJvdmUgPSB3aGljaC5taW4oYWJzKHJhdGlvIC0gUGFyLmFib3ZlJHRoZXRhKSkKICAgIHBvcy5iZWxvdyA9IHdoaWNoLm1pbihhYnMocmF0aW8gLSBQYXIuYmVsb3ckdGhldGEpKQogICAgIyBMaW5lYXIgaW50ZXJwb2xhdGlvbgogICAgbG4ueCA9IGMoUGFyLmFib3ZlJGYxLm5vcm1bcG9zLmFib3ZlXSwgUGFyLmJlbG93JGYxLm5vcm1bcG9zLmJlbG93XSkKICAgIGxuLnkgPSBjKFBhci5hYm92ZSRmMi5ub3JtW3Bvcy5hYm92ZV0sIFBhci5iZWxvdyRmMi5ub3JtW3Bvcy5iZWxvd10pCiAgICBzbHAgPSBkaWZmKGxuLnkpL2RpZmYobG4ueCkKICAgICMgRmluZCB0aGUgcG9pbnQgb24gdGhlIHNlZ21lbnQgd2l0aCB0aGUgc2FtZSBhbmdsZSwgaWUuIHRoZSBzYW1lIHJhdGlvLgogICAgIyBTb2x2aW5nIHdpdGggdGhpcyBjb25zdHJhaW50IGhhcyBhbmFseXRpY2FsIHNvbHV0aW9uOgogICAgUGFyLnggPSAobG4ueVsxXSAtIHNscCpsbi54WzFdKSAvIChmMi5ub3JtL2YxLm5vcm0gLSBzbHApCiAgICBQYXIueSA9IHNscCooUGFyLnggLSBsbi54WzFdKSArIGxuLnlbMV0KICB9CiAgCiAgIyBMaW5lYXIgZGlzdGFuY2UgdG8gdGhlIGZyb250IGlzIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gZGlzdGFuY2VzIHRvIHRoZSBvcmlnaW4KICBkaXN0ID0gc3FydChmMS5ub3JtXjIgKyBmMi5ub3JtXjIpIC0gc3FydChQYXIueF4yICsgUGFyLnleMikKICByZXR1cm4oZGlzdCkKfQpgYGAKCkdhdXNzaWFuIHByb2Nlc3MgcGFyYW1ldGVyIHR1bmluZwpgYGB7cn0KZmlsbC5zYW1wbGUubW9kID0gZnVuY3Rpb24oR1Bhci5kYXRhLCBpbnB1dC5uYW1lLCBvdXRwdXQubmFtZSl7CiAgIyBDYWxjdWxhdGUgdGhlIEdQIG1vZGVsIHRvIHVzZS4gCiAgIyBVc2luZyB0aGUga20gZnVuY3Rpb24sIGJ1dCBhcHBsaWVzIGNoZWNrcyBvbiB0aGUgc3lzdGVtIHRvIG1ha2Ugc3VyZSB0aGF0IAogICMgdGhlIG1vZGVsIHVuY2VydGFpbnR5IG1hdGNoZXMgZXhwZWN0YXRpb25zIGJhc2VkIG9uIEdQLCBpZS4gaXQgZGlkIG5vdAogICMgZmFpbCB0byBjb252ZXJnZS4KICAKICAjIEJhc2VkIG9uIHRlc3RpbmcsIHRoZSBtb2RlbCBpcyBiYWQgd2hlbiB0aGUgMTAlIHBlcmNlbnRpbGUgYW5kIDkwJSBwZXJjZW50aWxlIAogICMgb2YgdGhlIHN0YW5kYXJkIGRldmlhdGlvbiBhcmUgb2YgdGhlIHNhbWUgb3JkZXIgb2YgbWFnbml0dWRlLiBUaGlzIGlzIGVhc2llc3QKICAjIGNoZWNrZWQgaWYgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgMTB0aCBhbmQgOTB0aCBwZXJjZW50aWxlCiAgIyBpcyBsYXJnZXIgdGhhbiB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSAyNXRoIGFuZCA3NXRoLgogIHB0MTAgPSAxOyBwdDkwID0gMTsgcHQyNSA9IDE7IHB0NzUgPSAxCiAgd2hpbGUobG9nMTAocHQ5MC9wdDEwKSA8PSBsb2cxMChwdDc1L3B0MjUpKXsKICAgIG1vZC5vdXQgPSBEaWNlS3JpZ2luZzo6a20oZGVzaWduID0gR1Bhci5kYXRhWywgaW5wdXQubmFtZV0sIHJlc3BvbnNlID0gR1Bhci5kYXRhWywgb3V0cHV0Lm5hbWVdLCAKICAgICAgICAgICAgICAgICBjb3Z0eXAgPSAnZ2F1c3MnLCAjIEdhdXNzaWFuIHVuY2VydGFpbnR5CiAgICAgICAgICAgICAgICAgb3B0aW0ubWV0aG9kID0gJ2dlbicsICMgR2VuZXRpYyBhbGdvcml0aG0gb3B0aW1pemF0aW9uCiAgICAgICAgICAgICAgICAgY29udHJvbCA9IGxpc3QodHJhY2UgPSBGQUxTRSwgIyBUdXJuIG9mZiB0cmFja2luZyB0byBzaW1wbGlmeSBvdXRwdXQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwb3Auc2l6ZSA9IDUwLCAjIEluY3JlYXNlIHJvYnVzdG5lc3MKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXguZ2VuZXJhdGlvbnMgPSA0MDApLCAjIFNvbWUgY29udmVyZ2VuY2UgaXNzdWVzCiAgICAgICAgICAgICAgICAgbnVnZ2V0ID0gMWUtNiwgIyBBdm9pZCBlaWdlbnZhbHVlcyBvZiAwCiAgICAgICAgICAgICAgICAgKQogICAgCiAgICAjIFJhbmRvbWx5IHNhbXBsZSAyMDAgcG9pbnRzIGZyb20gdGhlIHNlYXJjaCBzcGFjZQogICAgcHQgPSAyMDA7IGkgPSAxCiAgICBsaW1zID0gcmFuZ2UoR1Bhci5kYXRhWyxpbnB1dC5uYW1lW2ldXSkKICAgIHNhbXAgPSBkYXRhLmZyYW1lKHJ1bmlmKG4gPSBwdCwgbWluID0gbGltc1sxXSwgbWF4ID0gbGltc1syXSkpCiAgICBmb3IoaSBpbiAyOmxlbmd0aChpbnB1dC5uYW1lKSl7CiAgICAgIGxpbXMgPSByYW5nZShHUGFyLmRhdGFbLGlucHV0Lm5hbWVbaV1dKQogICAgICBzYW1wWyxpXSA9IHJ1bmlmKG4gPSBwdCwgbWluID0gbGltc1sxXSwgbWF4ID0gbGltc1syXSkKICAgIH0KICAgIG5hbWVzKHNhbXApID0gaW5wdXQubmFtZQogICAgCiAgICAjIEZpbmQgbW9kZWwgb3V0cHV0IHRvIGZpbmQgdGhlIHBlcmNlbnRpbGUgcmFua3MgZm9yIHRoaXMgaXRlcmF0aW9uCiAgICByZXMgPSBwcmVkaWN0KG9iamVjdCA9IG1vZC5vdXQsIG5ld2RhdGEgPSBzYW1wLCB0eXBlID0gJ1VLJykKICAgIHB0MTAgPSBxdWFudGlsZShyZXMkc2QsIDAuMTApOyBwdDkwID0gcXVhbnRpbGUocmVzJHNkLCAwLjkwKQogICAgcHQyNSA9IHF1YW50aWxlKHJlcyRzZCwgMC4yNSk7IHB0NzUgPSBxdWFudGlsZShyZXMkc2QsIDAuNzUpCiAgfQogIHJldHVybihtb2Qub3V0KQp9CmBgYAoKIyBDb21wYXJpc29uIG9mIEdQLWJhc2VkIEJvdW5kYXJpZXMgd2l0aCBEaWZmZXJlbnQgQWNjZXB0YW5jZSBDcml0ZXJpYQoKQ29tcGFyaXNvbiBvZiB0aGUgYm91bmRhcmllcyB0byBzaG93IGhvdyBjaGFuZ2luZyB0aGUgYWNjZXB0YW5jZSBjcml0ZXJpYSBjaGFuZ2VzIHRoZSBzaGFwZSBvZiB0aGUgbmVhci1QYXJldG8gc2V0LiBTaG93Y2FzZXMgdGhlIHJvYnVzdG5lc3MgdG8gZGlmZmVyZW50IHNlbGVjdGlvbiBjcml0ZXJpYSwgaW5kaWNhdGluZyBmbGV4aWJpbGl0eSBpbiB0aGUgZGVzaWduIG9iamVjdGl2ZXMuCgpgYGB7ciBHUDogTG9hZGluZyBEYXRhfQojIyBMb2FkaW5nCiMgTG9hZCBkYXRhc2V0cyBmb3Igb2J0YWluaW5nIHRoZSByZWZpbmVkIHByb2JhYmlsaXR5IGZ1bmN0aW9ucwpkYXRhLmRlbHRhID0gcmVhZC5jc3YoJy4uL0V4X1F1YXJ0aWMvR1Bhcl9BY2NlcHRfRGVsdGExLmNzdicpCmRhdGEuY3V0b2YgPSByZWFkLmNzdignLi4vRXhfUXVhcnRpYy9HUGFyX0FjY2VwdF9UaHJlc2hvbGQuY3N2JykKZGF0YS5yYWRhbiA9IHJlYWQuY3N2KCcuLi9FeF9RdWFydGljL0dQYXJfQWNjZXB0X1JhZGl1cy5jc3YnKQojIExvYWQgZGF0YXNldHMgcHJpb3IgdG8gcmVmaW5lbWVudApkYXRhLnBhcmV0ID0gcmVhZC5jc3YoJy4uL0V4X1F1YXJ0aWMvR1Bhcl9hbGxfc3RhcnQuY3N2JykKZGF0YS5wYXJldCRyYWQgPSBzcXJ0KGRhdGEucGFyZXQkZjEubm9ybV4yICsgZGF0YS5wYXJldCRmMi5ub3JtXjIpCiMgQ29tcGFyZSB0byB0aGUgZXN0aW1hdGUgb2YgdGhlIFBhcmV0byBmcm9udGllcgpHUGFyLmZyb250ID0gcmVhZC5jc3YoZmlsZSA9ICcuLi9FeF9RdWFydGljL0dQYXJfZm50X3N0YXJ0LmNzdicpCgojIEFkZCBhIHJhbmRvbSBzYW1wbGVzIHRvIHNpbXVsYXRlIHRoZSBlZmZlY3Qgb2Ygc2FtcGxlIHNpemUgcmF0aGVyIHRoYW4gYWRhcHRpdmUgc2FtcGxpbmcKIyBIYXZlIGl0IHN0b3JlZCBzbyBmb3IgY29uc2lzdGVuY3ksIGJ1dCBhbHNvIGNyZWF0ZSBtdWx0aXBsZSByYW5kb20gc2FtcGxlIHNldHMKIyB0byBnZXQgYSBzZW5zZSBvZiB0aGUgdmFyaWFuY2UKaWYoZmlsZS5leGlzdHMoJ0dQYXJfUmFuZG9tLmNzdicpID09IEZBTFNFKXsKICBuc2FtcCA9IG1heChucm93KGRhdGEuZGVsdGEpLCBucm93KGRhdGEuY3V0b2YpLCBucm93KGRhdGEucmFkYW4pKSAtIG5yb3coZGF0YS5wYXJldCkKICBuc2FtcCA9IG5zYW1wKjEwICMgQ29sbGVjdCBtb3JlIHRoYW4gbmVlZGVkIGZvciBzYW1wbGUgc2l6ZSB2YXJpYW5jZSB0ZXN0aW5nCiAgZGF0YS5yYW5kbyA9IGRhdGEuZnJhbWUoeDEgPSBydW5pZihuID0gbnNhbXAsIG1pbiA9IDAsIG1heCA9IDUpLAogICAgICAgICAgICAgICAgICAgICAgICAgIHgyID0gcnVuaWYobiA9IG5zYW1wLCBtaW4gPSAwLCBtYXggPSA1KSkKICBkYXRhLnJhbmRvJGYxID0gZjEoeDEgPSBkYXRhLnJhbmRvJHgxLCB4MiA9IGRhdGEucmFuZG8keDIpCiAgZGF0YS5yYW5kbyRmMiA9IGYyKHgxID0gZGF0YS5yYW5kbyR4MSwgeDIgPSBkYXRhLnJhbmRvJHgyKQogICMgRmlsbCBpbiB0aGUgcmVtYWluaW5nIGNhbGN1bGF0aW9uczogbm9ybWFsaXplZCBvdXRwdXRzLCBkaXN0YW5jZSwgdGhldGEsIG9yZGVyCiAgZGF0YS5yYW5kbyA9IG4ub2JqKEdQYXIuZGF0YSA9IGRhdGEucmFuZG8sIEdQYXIuZnJvbnQgPSBHUGFyLmZyb250KQogIGNsID0gbWFrZUNsdXN0ZXIoMikKICByZWdpc3RlckRvUGFyYWxsZWwoY2wpCiAgZGlzdCA9IGZvcmVhY2gocm93ID0gMTpucm93KGRhdGEucmFuZG8pKSAlZG9wYXIlCiAgICBuLmRpc3QoZjEubm9ybSA9IGRhdGEucmFuZG8kZjEubm9ybVtyb3ddLCBmMi5ub3JtID0gZGF0YS5yYW5kbyRmMi5ub3JtW3Jvd10sIEdQYXIuZnJvbnQgPSBHUGFyLmZyb250KQogIHN0b3BDbHVzdGVyKGNsKQogIGRhdGEucmFuZG8kZGlzdCA9IHVubGlzdChkaXN0KQogIGRhdGEucmFuZG8kcmFkID0gc3FydChkYXRhLnJhbmRvJGYxLm5vcm1eMiArIGRhdGEucmFuZG8kZjIubm9ybV4yKQogIGRhdGEucmFuZG8kdGhldGEgPSBhdGFuKGRhdGEucmFuZG8kZjIubm9ybS9kYXRhLnJhbmRvJGYxLm5vcm0pKjE4MC9waSoxMC85CiAgZGF0YS5yYW5kbyRvcmRlciA9IHNlcShmcm9tID0gbWF4KGRhdGEucGFyZXQkb3JkZXIpICsgMSwgdG8gPSBtYXgoZGF0YS5wYXJldCRvcmRlcikgKyBuc2FtcCwgYnkgPSAxKQogIGRhdGEucmFuZG8gPSByYmluZChkYXRhLnBhcmV0WywgbmFtZXMoZGF0YS5wYXJldCkgJWluJSBuYW1lcyhkYXRhLnJhbmRvKV0sIGRhdGEucmFuZG8pCgogICMgU3RvcmUgdGhlIHJhbmRvbSBkYXRhCiAgd3JpdGUuY3N2KGRhdGEucmFuZG8sIGZpbGUgPSAnR1Bhcl9SYW5kb20uY3N2Jywgcm93Lm5hbWVzID0gRikKfQpkYXRhLnJhbmRvID0gcmVhZC5jc3YoZmlsZSA9ICdHUGFyX1JhbmRvbS5jc3YnKQpkYXRhLnJhbmRvID0gZGF0YS5yYW5kb1sxOm5yb3coZGF0YS5kZWx0YSksXSAjIEtlZXAgc2FtZSBzYW1wbGUgc2l6ZSBmb3IgaW5pdGlhbCB0ZXN0aW5nCgojIwojIEdyaWQgb2YgdGhlIHJlbGV2YW50IHJlZ2lvbiB0byB2aXN1YWxpemUgYW5kIGNvbXBhcmUKbG93ZXIgPSBjKDAsIDApOyB1cHBlciA9IGMoNSw1KTsgZ3JpZC5zeiA9IDEwMApmaW5lLmdyaWQgPSBleHBhbmQuZ3JpZCh4MSA9IHNlcShmcm9tID0gbG93ZXJbMV0sIHRvID0gdXBwZXJbMV0sIGxlbmd0aC5vdXQgPSBncmlkLnN6KSwgCiAgICAgICAgICAgICAgICAgICAgICAgIHgyID0gc2VxKGZyb20gPSBsb3dlclsyXSwgdG8gPSB1cHBlclsyXSwgbGVuZ3RoLm91dCA9IGdyaWQuc3opKQpmaW5lLmdyaWQgPSBmaW5lLmdyaWRbLDE6Ml0KbmFtZXMoZmluZS5ncmlkKSA9IGMoJ3gxJywgJ3gyJykKIyBDYWxjdWxhdGUgdGhlIGFjdHVhbCByZXN1bHRzCmZpbmUuZ3JpZCRmMSA9IGYxKHgxID0gZmluZS5ncmlkJHgxLCB4MiA9IGZpbmUuZ3JpZCR4MikKZmluZS5ncmlkJGYyID0gZjIoeDEgPSBmaW5lLmdyaWQkeDEsIHgyID0gZmluZS5ncmlkJHgyKQpmaW5lLmdyaWQgPSBuLm9iaihHUGFyLmRhdGEgPSBmaW5lLmdyaWQsIEdQYXIuZnJvbnQgPSBHUGFyLmZyb250KQpmaW5lLmdyaWQkZGlzdCA9IE5hTgpmb3Iocm93IGluIDE6bnJvdyhmaW5lLmdyaWQpKXsKICBmaW5lLmdyaWQkZGlzdFtyb3ddID0gbi5kaXN0KGYxLm5vcm0gPSBmaW5lLmdyaWQkZjEubm9ybVtyb3ddLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGYyLm5vcm0gPSBmaW5lLmdyaWQkZjIubm9ybVtyb3ddLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEdQYXIuZnJvbnQgPSBHUGFyLmZyb250KQp9CmZpbmUuZ3JpZCRhbmcgPSBhdGFuKGZpbmUuZ3JpZCRmMi5ub3JtL2ZpbmUuZ3JpZCRmMS5ub3JtKSoxODAvcGkqMTAvOQpmaW5lLmdyaWQkcmFkID0gc3FydChmaW5lLmdyaWQkZjEubm9ybV4yICsgZmluZS5ncmlkJGYyLm5vcm1eMikKYGBgCgpgYGB7ciBHUDogR2VuZXJhdGluZyBNb2RlbHN9CiMjCiMgTm9ybWFsaXplZCBkaXN0YW5jZQpmaW5lLmdyaWQkZGVsdGEgPSAwCmZpbmUuZ3JpZCRkZWx0YVtmaW5lLmdyaWQkZGlzdCA8PSAxXSA9IDEKCm1vZC5kaXN0LnBhcmV0ID0gZmlsbC5zYW1wbGUubW9kKEdQYXIuZGF0YSA9IGRhdGEucGFyZXQsIGlucHV0Lm5hbWUgPSBjKCd4MScsICd4MicpLCBvdXRwdXQubmFtZSA9ICdkaXN0JykKbW9kLmRpc3QuYWRhcHQgPSBmaWxsLnNhbXBsZS5tb2QoR1Bhci5kYXRhID0gZGF0YS5kZWx0YSwgaW5wdXQubmFtZSA9IGMoJ3gxJywgJ3gyJyksIG91dHB1dC5uYW1lID0gJ2Rpc3QnKQptb2QuZGlzdC5yYW5kbyA9IGZpbGwuc2FtcGxlLm1vZChHUGFyLmRhdGEgPSBkYXRhLnJhbmRvLCBpbnB1dC5uYW1lID0gYygneDEnLCAneDInKSwgb3V0cHV0Lm5hbWUgPSAnZGlzdCcpCgpyZXMgPSBwcmVkaWN0KG9iamVjdCA9IG1vZC5kaXN0LnBhcmV0LCBuZXdkYXRhID0gZmluZS5ncmlkWyxjKCd4MScsICd4MicpXSwgdHlwZSA9ICJVSyIpCmZpbmUuZ3JpZCRwcm9iLmRlbHRhLnBhcmV0ID0gcG5vcm0ocSA9IDAsIG1lYW4gPSByZXMkbWVhbiAtIDEsIHNkID0gcmVzJHNkKQpyZXMgPSBwcmVkaWN0KG9iamVjdCA9IG1vZC5kaXN0LmFkYXB0LCBuZXdkYXRhID0gZmluZS5ncmlkWyxjKCd4MScsICd4MicpXSwgdHlwZSA9ICJVSyIpCmZpbmUuZ3JpZCRwcm9iLmRlbHRhLmFkYXB0ID0gcG5vcm0ocSA9IDAsIG1lYW4gPSByZXMkbWVhbiAtIDEsIHNkID0gcmVzJHNkKQpyZXMgPSBwcmVkaWN0KG9iamVjdCA9IG1vZC5kaXN0LnJhbmRvLCBuZXdkYXRhID0gZmluZS5ncmlkWyxjKCd4MScsICd4MicpXSwgdHlwZSA9ICJVSyIpCmZpbmUuZ3JpZCRwcm9iLmRlbHRhLnJhbmRvID0gcG5vcm0ocSA9IDAsIG1lYW4gPSByZXMkbWVhbiAtIDEsIHNkID0gcmVzJHNkKQoKIyMKIyBUaHJlc2hvbGQgY3V0b2ZmCmZpbmUuZ3JpZCRjdXRvZiA9IDAKZmluZS5ncmlkJGN1dG9mW2ZpbmUuZ3JpZCRmMS5ub3JtIDw9IDEgJiBmaW5lLmdyaWQkZjIubm9ybSA8PSAxXSA9IDEKCm1vZC5mMS5wYXJldCA9IGZpbGwuc2FtcGxlLm1vZChHUGFyLmRhdGEgPSBkYXRhLnBhcmV0LCBpbnB1dC5uYW1lID0gYygneDEnLCAneDInKSwgb3V0cHV0Lm5hbWUgPSAnZjEubm9ybScpCm1vZC5mMi5wYXJldCA9IGZpbGwuc2FtcGxlLm1vZChHUGFyLmRhdGEgPSBkYXRhLnBhcmV0LCBpbnB1dC5uYW1lID0gYygneDEnLCAneDInKSwgb3V0cHV0Lm5hbWUgPSAnZjIubm9ybScpCm1vZC5mMS5hZGFwdCA9IGZpbGwuc2FtcGxlLm1vZChHUGFyLmRhdGEgPSBkYXRhLmN1dG9mLCBpbnB1dC5uYW1lID0gYygneDEnLCAneDInKSwgb3V0cHV0Lm5hbWUgPSAnZjEubm9ybScpCm1vZC5mMi5hZGFwdCA9IGZpbGwuc2FtcGxlLm1vZChHUGFyLmRhdGEgPSBkYXRhLmN1dG9mLCBpbnB1dC5uYW1lID0gYygneDEnLCAneDInKSwgb3V0cHV0Lm5hbWUgPSAnZjIubm9ybScpCm1vZC5mMS5yYW5kbyA9IGZpbGwuc2FtcGxlLm1vZChHUGFyLmRhdGEgPSBkYXRhLnJhbmRvLCBpbnB1dC5uYW1lID0gYygneDEnLCAneDInKSwgb3V0cHV0Lm5hbWUgPSAnZjEubm9ybScpCm1vZC5mMi5yYW5kbyA9IGZpbGwuc2FtcGxlLm1vZChHUGFyLmRhdGEgPSBkYXRhLnJhbmRvLCBpbnB1dC5uYW1lID0gYygneDEnLCAneDInKSwgb3V0cHV0Lm5hbWUgPSAnZjIubm9ybScpCgpyZXMxID0gcHJlZGljdChvYmplY3QgPSBtb2QuZjEucGFyZXQsIG5ld2RhdGEgPSBkYXRhLmZyYW1lKHgxID0gZmluZS5ncmlkJHgxLCB4MiA9IGZpbmUuZ3JpZCR4MiksIHR5cGUgPSAiVUsiKQpyZXMyID0gcHJlZGljdChvYmplY3QgPSBtb2QuZjIucGFyZXQsIG5ld2RhdGEgPSBkYXRhLmZyYW1lKHgxID0gZmluZS5ncmlkJHgxLCB4MiA9IGZpbmUuZ3JpZCR4MiksIHR5cGUgPSAiVUsiKQpmaW5lLmdyaWQkcHJvYi5jdXRvZi5wYXJldCA9IHBub3JtKHEgPSAwLCBtZWFuID0gcmVzMSRtZWFuIC0gMSwgc2QgPSByZXMxJHNkKSAqIAogIHBub3JtKHEgPSAwLCBtZWFuID0gcmVzMiRtZWFuIC0gMSwgc2QgPSByZXMyJHNkKQpyZXMxID0gcHJlZGljdChvYmplY3QgPSBtb2QuZjEuYWRhcHQsIG5ld2RhdGEgPSBkYXRhLmZyYW1lKHgxID0gZmluZS5ncmlkJHgxLCB4MiA9IGZpbmUuZ3JpZCR4MiksIHR5cGUgPSAiVUsiKQpyZXMyID0gcHJlZGljdChvYmplY3QgPSBtb2QuZjIuYWRhcHQsIG5ld2RhdGEgPSBkYXRhLmZyYW1lKHgxID0gZmluZS5ncmlkJHgxLCB4MiA9IGZpbmUuZ3JpZCR4MiksIHR5cGUgPSAiVUsiKQpmaW5lLmdyaWQkcHJvYi5jdXRvZi5hZGFwdCA9IHBub3JtKHEgPSAwLCBtZWFuID0gcmVzMSRtZWFuIC0gMSwgc2QgPSByZXMxJHNkKSAqIAogIHBub3JtKHEgPSAwLCBtZWFuID0gcmVzMiRtZWFuIC0gMSwgc2QgPSByZXMyJHNkKQpyZXMxID0gcHJlZGljdChvYmplY3QgPSBtb2QuZjEucmFuZG8sIG5ld2RhdGEgPSBkYXRhLmZyYW1lKHgxID0gZmluZS5ncmlkJHgxLCB4MiA9IGZpbmUuZ3JpZCR4MiksIHR5cGUgPSAiVUsiKQpyZXMyID0gcHJlZGljdChvYmplY3QgPSBtb2QuZjIucmFuZG8sIG5ld2RhdGEgPSBkYXRhLmZyYW1lKHgxID0gZmluZS5ncmlkJHgxLCB4MiA9IGZpbmUuZ3JpZCR4MiksIHR5cGUgPSAiVUsiKQpmaW5lLmdyaWQkcHJvYi5jdXRvZi5yYW5kbyA9IHBub3JtKHEgPSAwLCBtZWFuID0gcmVzMSRtZWFuIC0gMSwgc2QgPSByZXMxJHNkKSAqIAogIHBub3JtKHEgPSAwLCBtZWFuID0gcmVzMiRtZWFuIC0gMSwgc2QgPSByZXMyJHNkKQoKIyMKIyBSYWRpdXMtYW5nbGUKZmluZS5ncmlkJHJhZGFuID0gMApmaW5lLmdyaWQkcmFkYW5bZmluZS5ncmlkJHJhZCA8PSAxICYgZmluZS5ncmlkJGFuZyA+IDIwXSA9IDEKCm1vZC5yYWQucGFyZXQgPSBmaWxsLnNhbXBsZS5tb2QoR1Bhci5kYXRhID0gZGF0YS5wYXJldCwgaW5wdXQubmFtZSA9IGMoJ3gxJywgJ3gyJyksIG91dHB1dC5uYW1lID0gJ3JhZCcpCm1vZC5hbmcucGFyZXQgPSBmaWxsLnNhbXBsZS5tb2QoR1Bhci5kYXRhID0gZGF0YS5wYXJldCwgaW5wdXQubmFtZSA9IGMoJ3gxJywgJ3gyJyksIG91dHB1dC5uYW1lID0gJ3RoZXRhJykKbW9kLnJhZC5hZGFwdCA9IGZpbGwuc2FtcGxlLm1vZChHUGFyLmRhdGEgPSBkYXRhLnJhZGFuLCBpbnB1dC5uYW1lID0gYygneDEnLCAneDInKSwgb3V0cHV0Lm5hbWUgPSAncmFkJykKbW9kLmFuZy5hZGFwdCA9IGZpbGwuc2FtcGxlLm1vZChHUGFyLmRhdGEgPSBkYXRhLnJhZGFuLCBpbnB1dC5uYW1lID0gYygneDEnLCAneDInKSwgb3V0cHV0Lm5hbWUgPSAndGhldGEnKQptb2QucmFkLnJhbmRvID0gZmlsbC5zYW1wbGUubW9kKEdQYXIuZGF0YSA9IGRhdGEucmFuZG8sIGlucHV0Lm5hbWUgPSBjKCd4MScsICd4MicpLCBvdXRwdXQubmFtZSA9ICdyYWQnKQptb2QuYW5nLnJhbmRvID0gZmlsbC5zYW1wbGUubW9kKEdQYXIuZGF0YSA9IGRhdGEucmFuZG8sIGlucHV0Lm5hbWUgPSBjKCd4MScsICd4MicpLCBvdXRwdXQubmFtZSA9ICd0aGV0YScpCgpyZXMxID0gcHJlZGljdChvYmplY3QgPSBtb2QucmFkLnBhcmV0LCBuZXdkYXRhID0gZGF0YS5mcmFtZSh4MSA9IGZpbmUuZ3JpZCR4MSwgeDIgPSBmaW5lLmdyaWQkeDIpLCB0eXBlID0gIlVLIikKcmVzMiA9IHByZWRpY3Qob2JqZWN0ID0gbW9kLmFuZy5wYXJldCwgbmV3ZGF0YSA9IGRhdGEuZnJhbWUoeDEgPSBmaW5lLmdyaWQkeDEsIHgyID0gZmluZS5ncmlkJHgyKSwgdHlwZSA9ICJVSyIpCmZpbmUuZ3JpZCRwcm9iLnJhZGFuLnBhcmV0ID0gcG5vcm0ocSA9IDAsIG1lYW4gPSByZXMxJG1lYW4gLSAxLCBzZCA9IHJlczEkc2QpICogCiAgKDEgLSBwbm9ybShxID0gMCwgbWVhbiA9IHJlczIkbWVhbiAtIDIwLCBzZCA9IHJlczIkc2QpKQpyZXMxID0gcHJlZGljdChvYmplY3QgPSBtb2QucmFkLmFkYXB0LCBuZXdkYXRhID0gZGF0YS5mcmFtZSh4MSA9IGZpbmUuZ3JpZCR4MSwgeDIgPSBmaW5lLmdyaWQkeDIpLCB0eXBlID0gIlVLIikKcmVzMiA9IHByZWRpY3Qob2JqZWN0ID0gbW9kLmFuZy5hZGFwdCwgbmV3ZGF0YSA9IGRhdGEuZnJhbWUoeDEgPSBmaW5lLmdyaWQkeDEsIHgyID0gZmluZS5ncmlkJHgyKSwgdHlwZSA9ICJVSyIpCmZpbmUuZ3JpZCRwcm9iLnJhZGFuLmFkYXB0ID0gcG5vcm0ocSA9IDAsIG1lYW4gPSByZXMxJG1lYW4gLSAxLCBzZCA9IHJlczEkc2QpICogCiAgKDEgLSBwbm9ybShxID0gMCwgbWVhbiA9IHJlczIkbWVhbiAtIDIwLCBzZCA9IHJlczIkc2QpKQpyZXMxID0gcHJlZGljdChvYmplY3QgPSBtb2QucmFkLnJhbmRvLCBuZXdkYXRhID0gZGF0YS5mcmFtZSh4MSA9IGZpbmUuZ3JpZCR4MSwgeDIgPSBmaW5lLmdyaWQkeDIpLCB0eXBlID0gIlVLIikKcmVzMiA9IHByZWRpY3Qob2JqZWN0ID0gbW9kLmFuZy5yYW5kbywgbmV3ZGF0YSA9IGRhdGEuZnJhbWUoeDEgPSBmaW5lLmdyaWQkeDEsIHgyID0gZmluZS5ncmlkJHgyKSwgdHlwZSA9ICJVSyIpCmZpbmUuZ3JpZCRwcm9iLnJhZGFuLnJhbmRvID0gcG5vcm0ocSA9IDAsIG1lYW4gPSByZXMxJG1lYW4gLSAxLCBzZCA9IHJlczEkc2QpICogCiAgKDEgLSBwbm9ybShxID0gMCwgbWVhbiA9IHJlczIkbWVhbiAtIDIwLCBzZCA9IHJlczIkc2QpKQoKcm0ocmVzMSwgcmVzMikKYGBgCgpgYGB7ciBHUDogUGxvdHRpbmcgVHJ1ZSBSZXN1bHRzfQpzZXAgPSAwCmdncGxvdCgpICsKICAjIEJvdW5kYXJpZXM6ICsvLSBzb21lIHNlcGFyYXRpb24gZnJvbSAwLjUKICBnZW9tX2NvbnRvdXIoZGF0YSA9IGZpbmUuZ3JpZCwgbWFwcGluZyA9IGFlcyh4ID0geDEsIHkgPSB4MiwgeiA9IHByb2IuZGVsdGEucGFyZXQsIGNvbG9yID0gJ2RlbHRhJyksIAogICAgICAgICAgICAgICBicmVha3MgPSBjKHNlcCwgLXNlcCkrMC41KSArCiAgZ2VvbV9jb250b3VyKGRhdGEgPSBmaW5lLmdyaWQsIG1hcHBpbmcgPSBhZXMoeCA9IHgxLCB5ID0geDIsIHogPSBwcm9iLmN1dG9mLnBhcmV0LCBjb2xvciA9ICdjdXRvZicpLCAKICAgICAgICAgICAgICAgYnJlYWtzID0gYyhzZXAsIC1zZXApKzAuNSkgKwogIGdlb21fY29udG91cihkYXRhID0gZmluZS5ncmlkLCBtYXBwaW5nID0gYWVzKHggPSB4MSwgeSA9IHgyLCB6ID0gcHJvYi5yYWRhbi5wYXJldCwgY29sb3IgPSAncmFkYW4nKSwgCiAgICAgICAgICAgICAgIGJyZWFrcyA9IGMoc2VwLCAtc2VwKSswLjUpICsKICAjIFBhcmV0byBmcm9udGllcgogIGdlb21fcG9pbnQoZGF0YSA9IEdQYXIuZnJvbnQsIG1hcHBpbmcgPSBhZXMoeCA9IHgxLCB5ID0geDIpLCBjb2xvciA9ICdibGFjaycpICsgCiAgZ2VvbV9zbW9vdGgoZGF0YSA9IEdQYXIuZnJvbnQsIG1hcHBpbmcgPSBhZXMoeCA9IHgxLCB5ID0geDIsIGNvbG9yID0gJ1BhcmV0bycpLCBsZXZlbCA9IDAuOTUsIG1ldGhvZCA9ICdsb2VzcycpICsgCiAgbGFicyh4ID0gZXhwcmVzc2lvbigneCdbMV0pLCB5ID0gZXhwcmVzc2lvbigneCdbMl0pLCBjb2xvciA9ICdBY2NlcHRhbmNlIENyaXRlcmlhJywgc3VidGl0bGUgPSAnQmVmb3JlIFJlZmluZW1lbnQnKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoJ2RlbHRhJyA9ICdyZWQnLCAnY3V0b2YnID0gJ3NreWJsdWUyJywgJ3JhZGFuJyA9ICdncmVlbicsICdQYXJldG8nID0gJ2JsYWNrJyksCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoJ2RlbHRhJyA9IGV4cHJlc3Npb24oZGVsdGEqJyA8IDEnKSwgJ2N1dG9mJyA9IGV4cHJlc3Npb24oJ0YnWzFdXicqJyonPCAxLCBGJ1syXV4nKicqJzwgMScpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAncmFkYW4nID0gZXhwcmVzc2lvbignciA8IDEsICcqdGhldGEqJyA+IDE4J14nbycpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdQYXJldG8nID0gZXhwcmVzc2lvbignUGFyZXRvIEZyb250JykpLAogICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBjKCdkZWx0YScsICdjdXRvZicsICdyYWRhbicsICdQYXJldG8nKSkgKwogIHRoZW1lX2NsYXNzaWMoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC44NSwgMC43NSkpICsgCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGxpbWl0cyA9IGMoMCwgNSkpICsgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGxpbWl0cyA9IGMoMCwgNSkpICsKICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3QoZmlsbCA9IGFscGhhKCd3aGl0ZScsIDEpKSkpCgpnZ3Bsb3QoKSArCiAgIyBCb3VuZGFyaWVzOiArLy0gc29tZSBzZXBhcmF0aW9uIGZyb20gMC41CiAgZ2VvbV9jb250b3VyKGRhdGEgPSBmaW5lLmdyaWQsIG1hcHBpbmcgPSBhZXMoeCA9IHgxLCB5ID0geDIsIHogPSBwcm9iLmRlbHRhLmFkYXB0LCBjb2xvciA9ICdkZWx0YScpLCAKICAgICAgICAgICAgICAgYnJlYWtzID0gYyhzZXAsIC1zZXApKzAuNSkgKwogIGdlb21fY29udG91cihkYXRhID0gZmluZS5ncmlkLCBtYXBwaW5nID0gYWVzKHggPSB4MSwgeSA9IHgyLCB6ID0gcHJvYi5jdXRvZi5hZGFwdCwgY29sb3IgPSAnY3V0b2YnKSwgCiAgICAgICAgICAgICAgIGJyZWFrcyA9IGMoc2VwLCAtc2VwKSswLjUpICsKICBnZW9tX2NvbnRvdXIoZGF0YSA9IGZpbmUuZ3JpZCwgbWFwcGluZyA9IGFlcyh4ID0geDEsIHkgPSB4MiwgeiA9IHByb2IucmFkYW4uYWRhcHQsIGNvbG9yID0gJ3JhZGFuJyksIAogICAgICAgICAgICAgICBicmVha3MgPSBjKHNlcCwgLXNlcCkrMC41KSArCiAgIyBQYXJldG8gZnJvbnRpZXIKICBnZW9tX3BvaW50KGRhdGEgPSBHUGFyLmZyb250LCBtYXBwaW5nID0gYWVzKHggPSB4MSwgeSA9IHgyKSwgY29sb3IgPSAnYmxhY2snKSArIAogIGdlb21fc21vb3RoKGRhdGEgPSBHUGFyLmZyb250LCBtYXBwaW5nID0gYWVzKHggPSB4MSwgeSA9IHgyLCBjb2xvciA9ICdQYXJldG8nKSwgbGV2ZWwgPSAwLjk1LCBtZXRob2QgPSAnbG9lc3MnKSArIAogIGxhYnMoeCA9IGV4cHJlc3Npb24oJ3gnWzFdKSwgeSA9IGV4cHJlc3Npb24oJ3gnWzJdKSwgY29sb3IgPSAnQWNjZXB0YW5jZSBDcml0ZXJpYScsIHN1YnRpdGxlID0gJ0FmdGVyIEFkYXB0aXZlIFNhbXBsaW5nIFJlZmluZW1lbnQnKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoJ2RlbHRhJyA9ICdyZWQnLCAnY3V0b2YnID0gJ3NreWJsdWUyJywgJ3JhZGFuJyA9ICdncmVlbicsICdQYXJldG8nID0gJ2JsYWNrJyksCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoJ2RlbHRhJyA9IGV4cHJlc3Npb24oZGVsdGEqJyA8IDEnKSwgJ2N1dG9mJyA9IGV4cHJlc3Npb24oJ0YnWzFdXicqJyonPCAxLCBGJ1syXV4nKicqJzwgMScpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAncmFkYW4nID0gZXhwcmVzc2lvbignciA8IDEsICcqdGhldGEqJyA+IDE4J14nbycpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdQYXJldG8nID0gZXhwcmVzc2lvbignUGFyZXRvIEZyb250JykpLAogICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBjKCdkZWx0YScsICdjdXRvZicsICdyYWRhbicsICdQYXJldG8nKSkgKwogIHRoZW1lX2NsYXNzaWMoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC44NSwgMC43NSkpICsgCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGxpbWl0cyA9IGMoMCwgNSkpICsgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGxpbWl0cyA9IGMoMCwgNSkpICsKICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3QoZmlsbCA9IGFscGhhKCd3aGl0ZScsIDEpKSkpCgpnZ3Bsb3QoKSArCiAgIyBCb3VuZGFyaWVzOiArLy0gc29tZSBzZXBhcmF0aW9uIGZyb20gMC41CiAgZ2VvbV9jb250b3VyKGRhdGEgPSBmaW5lLmdyaWQsIG1hcHBpbmcgPSBhZXMoeCA9IHgxLCB5ID0geDIsIHogPSBwcm9iLmRlbHRhLnJhbmRvLCBjb2xvciA9ICdkZWx0YScpLCAKICAgICAgICAgICAgICAgYnJlYWtzID0gYyhzZXAsIC1zZXApKzAuNSkgKwogIGdlb21fY29udG91cihkYXRhID0gZmluZS5ncmlkLCBtYXBwaW5nID0gYWVzKHggPSB4MSwgeSA9IHgyLCB6ID0gcHJvYi5jdXRvZi5yYW5kbywgY29sb3IgPSAnY3V0b2YnKSwgCiAgICAgICAgICAgICAgIGJyZWFrcyA9IGMoc2VwLCAtc2VwKSswLjUpICsKICBnZW9tX2NvbnRvdXIoZGF0YSA9IGZpbmUuZ3JpZCwgbWFwcGluZyA9IGFlcyh4ID0geDEsIHkgPSB4MiwgeiA9IHByb2IucmFkYW4ucmFuZG8sIGNvbG9yID0gJ3JhZGFuJyksIAogICAgICAgICAgICAgICBicmVha3MgPSBjKHNlcCwgLXNlcCkrMC41KSArCiAgIyBQYXJldG8gZnJvbnRpZXIKICBnZW9tX3BvaW50KGRhdGEgPSBHUGFyLmZyb250LCBtYXBwaW5nID0gYWVzKHggPSB4MSwgeSA9IHgyKSwgY29sb3IgPSAnYmxhY2snKSArIAogIGdlb21fc21vb3RoKGRhdGEgPSBHUGFyLmZyb250LCBtYXBwaW5nID0gYWVzKHggPSB4MSwgeSA9IHgyLCBjb2xvciA9ICdQYXJldG8nKSwgbGV2ZWwgPSAwLjk1LCBtZXRob2QgPSAnbG9lc3MnKSArIAogIGxhYnMoeCA9IGV4cHJlc3Npb24oJ3gnWzFdKSwgeSA9IGV4cHJlc3Npb24oJ3gnWzJdKSwgY29sb3IgPSAnQWNjZXB0YW5jZSBDcml0ZXJpYScsIHN1YnRpdGxlID0gJ0FmdGVyIFJhbmRvbSBTYW1wbGluZycpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygnZGVsdGEnID0gJ3JlZCcsICdjdXRvZicgPSAnc2t5Ymx1ZTInLCAncmFkYW4nID0gJ2dyZWVuJywgJ1BhcmV0bycgPSAnYmxhY2snKSwKICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygnZGVsdGEnID0gZXhwcmVzc2lvbihkZWx0YSonIDwgMScpLCAnY3V0b2YnID0gZXhwcmVzc2lvbignRidbMV1eJyonKic8IDEsIEYnWzJdXicqJyonPCAxJyksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdyYWRhbicgPSBleHByZXNzaW9uKCdyIDwgMSwgJyp0aGV0YSonID4gMTgnXidvJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1BhcmV0bycgPSBleHByZXNzaW9uKCdQYXJldG8gRnJvbnQnKSksCiAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IGMoJ2RlbHRhJywgJ2N1dG9mJywgJ3JhZGFuJywgJ1BhcmV0bycpKSArCiAgdGhlbWVfY2xhc3NpYygpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYygwLjg1LCAwLjc1KSkgKyAKICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgbGltaXRzID0gYygwLCA1KSkgKyBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgbGltaXRzID0gYygwLCA1KSkgKwogIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChmaWxsID0gYWxwaGEoJ3doaXRlJywgMSkpKSkKCmBgYAoKYGBge3IgR1A6IENvbXBhcmlzb24gb2Ygc2V0cyB0byB0aGUgcmVhbCByZXN1bHR9CiMgUGxvdHRpbmcgdmFyaWFibGVzCnNlcCA9IDAuMDU7IHRyYW5zID0gMC4yNTsgbG4uc3ogPSAxLjI1CgojIyBEaXN0YW5jZQpnZ3Bsb3QoKSArCiAgIyBCb3VuZGFyaWVzOiArLy0gc29tZSBzZXBhcmF0aW9uIGZyb20gMC41CiAgZ2VvbV9jb250b3VyX2ZpbGxlZChkYXRhID0gZmluZS5ncmlkLCBtYXBwaW5nID0gYWVzKHggPSB4MSwgeSA9IHgyLCB6ID0gcHJvYi5kZWx0YS5wYXJldCwgCiAgICAgICAgICAgICAgIGZpbGwgPSAncGFyZXQnKSwgCiAgICAgICAgICAgICAgIGJyZWFrcyA9IGMoc2VwLCAtc2VwKSswLjUsIGxpbmV0eXBlID0gMiwgYWxwaGEgPSB0cmFucykgKwogIGdlb21fY29udG91cl9maWxsZWQoZGF0YSA9IGZpbmUuZ3JpZCwgbWFwcGluZyA9IGFlcyh4ID0geDEsIHkgPSB4MiwgeiA9IHByb2IuZGVsdGEuYWRhcHQsIAogICAgICAgICAgICAgICBmaWxsID0gJ2FkYXB0JyksIAogICAgICAgICAgICAgICBicmVha3MgPSBjKHNlcCwgLXNlcCkrMC41LCBsaW5ldHlwZSA9IDIsIGFscGhhID0gdHJhbnMpICsKICBnZW9tX2NvbnRvdXJfZmlsbGVkKGRhdGEgPSBmaW5lLmdyaWQsIG1hcHBpbmcgPSBhZXMoeCA9IHgxLCB5ID0geDIsIHogPSBwcm9iLmRlbHRhLnJhbmRvLCAKICAgICAgICAgICAgICAgZmlsbCA9ICdyYW5kbycpLCAKICAgICAgICAgICAgICAgYnJlYWtzID0gYyhzZXAsIC1zZXApKzAuNSwgbGluZXR5cGUgPSAyLCBhbHBoYSA9IHRyYW5zKSArCiAgIyBBY3R1YWwgYm91bmRhcmllcwogIGdlb21fY29udG91cihkYXRhID0gZmluZS5ncmlkLCBtYXBwaW5nID0gYWVzKHggPSB4MSwgeSA9IHgyLCB6ID0gcHJvYi5kZWx0YS5wYXJldCwgY29sb3IgPSAncGFyZXQnKSwgCiAgICAgICAgICAgICAgIGJyZWFrcyA9IDAuNSwgbGluZXR5cGUgPSAxLCBzaXplID0gbG4uc3opICsKICBnZW9tX2NvbnRvdXIoZGF0YSA9IGZpbmUuZ3JpZCwgbWFwcGluZyA9IGFlcyh4ID0geDEsIHkgPSB4MiwgeiA9IHByb2IuZGVsdGEuYWRhcHQsIGNvbG9yID0gJ2FkYXB0JyksIAogICAgICAgICAgICAgICBicmVha3MgPSAwLjUsIGxpbmV0eXBlID0gMSwgc2l6ZSA9IGxuLnN6KSArCiAgZ2VvbV9jb250b3VyKGRhdGEgPSBmaW5lLmdyaWQsIG1hcHBpbmcgPSBhZXMoeCA9IHgxLCB5ID0geDIsIHogPSBwcm9iLmRlbHRhLnJhbmRvLCBjb2xvciA9ICdyYW5kbycpLCAKICAgICAgICAgICAgICAgYnJlYWtzID0gMC41LCBsaW5ldHlwZSA9IDEsIHNpemUgPSBsbi5zeikgKwogIGdlb21fY29udG91cihkYXRhID0gZmluZS5ncmlkLCBtYXBwaW5nID0gYWVzKHggPSB4MSwgeSA9IHgyLCB6ID0gZGVsdGEsIGNvbG9yID0gJ3RydScpLCAKICAgICAgICAgICAgICAgYnJlYWtzID0gMC41LCBsaW5ldHlwZSA9IDEsIHNpemUgPSBsbi5zeikgKwogIHNjYWxlX2NvbG9yX21hbnVhbChicmVha3MgPSBjKCd0cnUnLCAncGFyZXQnLCAnYWRhcHQnLCAncmFuZG8nKSwKICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygndHJ1JyA9ICdUcnVlIEJvdW5kYXJ5JywgJ3BhcmV0JyA9ICdTdGFydGluZyBEYXRhc2V0JywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ2FkYXB0JyA9ICcrIEFkYXB0aXZlIFNhbXBsaW5nJywgJ3JhbmRvJyA9ICcrIFJhbmRvbSBTYW1wbGluZycpLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBjKCd0cnUnID0gJ2JsYWNrJywgJ3BhcmV0JyA9ICdza3libHVlMicsICdhZGFwdCcgPSAncmVkJywgJ3JhbmRvJyA9ICdncmVlbicpKSArCiAgc2NhbGVfZmlsbF9tYW51YWwoYnJlYWtzID0gYygndHJ1JywgJ3BhcmV0JywgJ2FkYXB0JywgJ3JhbmRvJyksCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoJ3RydScgPSAnVHJ1ZSBCb3VuZGFyeScsICdwYXJldCcgPSAnU3RhcnRpbmcgRGF0YXNldCcsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdhZGFwdCcgPSAnKyBBZGFwdGl2ZSBTYW1wbGluZycsICdyYW5kbycgPSAnKyBSYW5kb20gU2FtcGxpbmcnKSwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYygndHJ1JyA9ICdibGFjaycsICdwYXJldCcgPSAnc2t5Ymx1ZTInLCAnYWRhcHQnID0gJ3JlZCcsICdyYW5kbycgPSAnZ3JlZW4nKSkgKwogIGxhYnMoeCA9IGV4cHJlc3Npb24oJ3gnWzFdKSwgeCA9IGV4cHJlc3Npb24oJ3gnWzJdKSwgc3VidGl0bGUgPSAnQ3JpdGVyaWE6IFBhcmV0byBEaXN0YW5jZScsIGNvbG9yID0gJycpICsKICB0aGVtZV9jbGFzc2ljKCkgKyBndWlkZXMoZmlsbCA9IEZBTFNFKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC44NSwgMC44NSkpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCA1KSwgZXhwYW5kID0gYygwLCAwKSkgKyBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCA1KSwgZXhwYW5kID0gYygwLCAwKSkKCiMjIFRocmVzaG9sZApnZ3Bsb3QoKSArCiAgIyBCb3VuZGFyaWVzOiArLy0gc29tZSBzZXBhcmF0aW9uIGZyb20gMC41CiAgZ2VvbV9jb250b3VyX2ZpbGxlZChkYXRhID0gZmluZS5ncmlkLCBtYXBwaW5nID0gYWVzKHggPSB4MSwgeSA9IHgyLCB6ID0gcHJvYi5jdXRvZi5wYXJldCwgCiAgICAgICAgICAgICAgIGZpbGwgPSAncGFyZXQnKSwgCiAgICAgICAgICAgICAgIGJyZWFrcyA9IGMoc2VwLCAtc2VwKSswLjUsIGxpbmV0eXBlID0gMiwgYWxwaGEgPSB0cmFucykgKwogIGdlb21fY29udG91cl9maWxsZWQoZGF0YSA9IGZpbmUuZ3JpZCwgbWFwcGluZyA9IGFlcyh4ID0geDEsIHkgPSB4MiwgeiA9IHByb2IuY3V0b2YuYWRhcHQsIAogICAgICAgICAgICAgICBmaWxsID0gJ2FkYXB0JyksIAogICAgICAgICAgICAgICBicmVha3MgPSBjKHNlcCwgLXNlcCkrMC41LCBsaW5ldHlwZSA9IDIsIGFscGhhID0gdHJhbnMpICsKICBnZW9tX2NvbnRvdXJfZmlsbGVkKGRhdGEgPSBmaW5lLmdyaWQsIG1hcHBpbmcgPSBhZXMoeCA9IHgxLCB5ID0geDIsIHogPSBwcm9iLmN1dG9mLnJhbmRvLCAKICAgICAgICAgICAgICAgZmlsbCA9ICdyYW5kbycpLCAKICAgICAgICAgICAgICAgYnJlYWtzID0gYyhzZXAsIC1zZXApKzAuNSwgbGluZXR5cGUgPSAyLCBhbHBoYSA9IHRyYW5zKSArCiAgIyBBY3R1YWwgYm91bmRhcmllcwogIGdlb21fY29udG91cihkYXRhID0gZmluZS5ncmlkLCBtYXBwaW5nID0gYWVzKHggPSB4MSwgeSA9IHgyLCB6ID0gcHJvYi5jdXRvZi5wYXJldCwgY29sb3IgPSAncGFyZXQnKSwgCiAgICAgICAgICAgICAgIGJyZWFrcyA9IDAuNSwgbGluZXR5cGUgPSAxLCBzaXplID0gbG4uc3opICsKICBnZW9tX2NvbnRvdXIoZGF0YSA9IGZpbmUuZ3JpZCwgbWFwcGluZyA9IGFlcyh4ID0geDEsIHkgPSB4MiwgeiA9IHByb2IuY3V0b2YuYWRhcHQsIGNvbG9yID0gJ2FkYXB0JyksIAogICAgICAgICAgICAgICBicmVha3MgPSAwLjUsIGxpbmV0eXBlID0gMSwgc2l6ZSA9IGxuLnN6KSArCiAgZ2VvbV9jb250b3VyKGRhdGEgPSBmaW5lLmdyaWQsIG1hcHBpbmcgPSBhZXMoeCA9IHgxLCB5ID0geDIsIHogPSBwcm9iLmN1dG9mLnJhbmRvLCBjb2xvciA9ICdyYW5kbycpLCAKICAgICAgICAgICAgICAgYnJlYWtzID0gMC41LCBsaW5ldHlwZSA9IDEsIHNpemUgPSBsbi5zeikgKwogIGdlb21fY29udG91cihkYXRhID0gZmluZS5ncmlkLCBtYXBwaW5nID0gYWVzKHggPSB4MSwgeSA9IHgyLCB6ID0gY3V0b2YsIGNvbG9yID0gJ3RydScpLCAKICAgICAgICAgICAgICAgYnJlYWtzID0gMC41LCBsaW5ldHlwZSA9IDEsIHNpemUgPSBsbi5zeikgKwogIHNjYWxlX2NvbG9yX21hbnVhbChicmVha3MgPSBjKCd0cnUnLCAncGFyZXQnLCAnYWRhcHQnLCAncmFuZG8nKSwKICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygndHJ1JyA9ICdUcnVlIEJvdW5kYXJ5JywgJ3BhcmV0JyA9ICdTdGFydGluZyBEYXRhc2V0JywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ2FkYXB0JyA9ICcrIEFkYXB0aXZlIFNhbXBsaW5nJywgJ3JhbmRvJyA9ICcrIFJhbmRvbSBTYW1wbGluZycpLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBjKCd0cnUnID0gJ2JsYWNrJywgJ3BhcmV0JyA9ICdza3libHVlMicsICdhZGFwdCcgPSAncmVkJywgJ3JhbmRvJyA9ICdncmVlbicpKSArCiAgc2NhbGVfZmlsbF9tYW51YWwoYnJlYWtzID0gYygndHJ1JywgJ3BhcmV0JywgJ2FkYXB0JywgJ3JhbmRvJyksCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoJ3RydScgPSAnVHJ1ZSBCb3VuZGFyeScsICdwYXJldCcgPSAnU3RhcnRpbmcgRGF0YXNldCcsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdhZGFwdCcgPSAnKyBBZGFwdGl2ZSBTYW1wbGluZycsICdyYW5kbycgPSAnKyBSYW5kb20gU2FtcGxpbmcnKSwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYygndHJ1JyA9ICdibGFjaycsICdwYXJldCcgPSAnc2t5Ymx1ZTInLCAnYWRhcHQnID0gJ3JlZCcsICdyYW5kbycgPSAnZ3JlZW4nKSkgKwogIGxhYnMoeCA9IGV4cHJlc3Npb24oJ3gnWzFdKSwgeCA9IGV4cHJlc3Npb24oJ3gnWzJdKSwgc3VidGl0bGUgPSAnQ3JpdGVyaWE6IE9iamVjdGl2ZSBmdW5jdGlvbiB2YWx1ZXMnLCBjb2xvciA9ICcnKSArCiAgdGhlbWVfY2xhc3NpYygpICsgZ3VpZGVzKGZpbGwgPSBGQUxTRSkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuODUsIDAuODUpKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgNSksIGV4cGFuZCA9IGMoMCwgMCkpICsgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgNSksIGV4cGFuZCA9IGMoMCwgMCkpCgojIyBSYWRpdXMtYW5nbGUKZ2dwbG90KCkgKwogICMgQm91bmRhcmllczogKy8tIHNvbWUgc2VwYXJhdGlvbiBmcm9tIDAuNQogIGdlb21fY29udG91cl9maWxsZWQoZGF0YSA9IGZpbmUuZ3JpZCwgbWFwcGluZyA9IGFlcyh4ID0geDEsIHkgPSB4MiwgeiA9IHByb2IucmFkYW4ucGFyZXQsIAogICAgICAgICAgICAgICBmaWxsID0gJ3BhcmV0JyksIAogICAgICAgICAgICAgICBicmVha3MgPSBjKHNlcCwgLXNlcCkrMC41LCBsaW5ldHlwZSA9IDIsIGFscGhhID0gdHJhbnMpICsKICBnZW9tX2NvbnRvdXJfZmlsbGVkKGRhdGEgPSBmaW5lLmdyaWQsIG1hcHBpbmcgPSBhZXMoeCA9IHgxLCB5ID0geDIsIHogPSBwcm9iLnJhZGFuLmFkYXB0LCAKICAgICAgICAgICAgICAgZmlsbCA9ICdhZGFwdCcpLCAKICAgICAgICAgICAgICAgYnJlYWtzID0gYyhzZXAsIC1zZXApKzAuNSwgbGluZXR5cGUgPSAyLCBhbHBoYSA9IHRyYW5zKSArCiAgZ2VvbV9jb250b3VyX2ZpbGxlZChkYXRhID0gZmluZS5ncmlkLCBtYXBwaW5nID0gYWVzKHggPSB4MSwgeSA9IHgyLCB6ID0gcHJvYi5yYWRhbi5yYW5kbywgCiAgICAgICAgICAgICAgIGZpbGwgPSAncmFuZG8nKSwgCiAgICAgICAgICAgICAgIGJyZWFrcyA9IGMoc2VwLCAtc2VwKSswLjUsIGxpbmV0eXBlID0gMiwgYWxwaGEgPSB0cmFucykgKwogICMgQWN0dWFsIGJvdW5kYXJpZXMKICBnZW9tX2NvbnRvdXIoZGF0YSA9IGZpbmUuZ3JpZCwgbWFwcGluZyA9IGFlcyh4ID0geDEsIHkgPSB4MiwgeiA9IHByb2IucmFkYW4ucGFyZXQsIGNvbG9yID0gJ3BhcmV0JyksIAogICAgICAgICAgICAgICBicmVha3MgPSAwLjUsIGxpbmV0eXBlID0gMSwgc2l6ZSA9IGxuLnN6KSArCiAgZ2VvbV9jb250b3VyKGRhdGEgPSBmaW5lLmdyaWQsIG1hcHBpbmcgPSBhZXMoeCA9IHgxLCB5ID0geDIsIHogPSBwcm9iLnJhZGFuLmFkYXB0LCBjb2xvciA9ICdhZGFwdCcpLCAKICAgICAgICAgICAgICAgYnJlYWtzID0gMC41LCBsaW5ldHlwZSA9IDEsIHNpemUgPSBsbi5zeikgKwogIGdlb21fY29udG91cihkYXRhID0gZmluZS5ncmlkLCBtYXBwaW5nID0gYWVzKHggPSB4MSwgeSA9IHgyLCB6ID0gcHJvYi5yYWRhbi5yYW5kbywgY29sb3IgPSAncmFuZG8nKSwgCiAgICAgICAgICAgICAgIGJyZWFrcyA9IDAuNSwgbGluZXR5cGUgPSAxLCBzaXplID0gbG4uc3opICsKICBnZW9tX2NvbnRvdXIoZGF0YSA9IGZpbmUuZ3JpZCwgbWFwcGluZyA9IGFlcyh4ID0geDEsIHkgPSB4MiwgeiA9IHJhZGFuLCBjb2xvciA9ICd0cnUnKSwgCiAgICAgICAgICAgICAgIGJyZWFrcyA9IDAuNSwgbGluZXR5cGUgPSAxLCBzaXplID0gbG4uc3opICsKICBzY2FsZV9jb2xvcl9tYW51YWwoYnJlYWtzID0gYygndHJ1JywgJ3BhcmV0JywgJ2FkYXB0JywgJ3JhbmRvJyksCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoJ3RydScgPSAnVHJ1ZSBCb3VuZGFyeScsICdwYXJldCcgPSAnU3RhcnRpbmcgRGF0YXNldCcsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdhZGFwdCcgPSAnKyBBZGFwdGl2ZSBTYW1wbGluZycsICdyYW5kbycgPSAnKyBSYW5kb20gU2FtcGxpbmcnKSwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYygndHJ1JyA9ICdibGFjaycsICdwYXJldCcgPSAnc2t5Ymx1ZTInLCAnYWRhcHQnID0gJ3JlZCcsICdyYW5kbycgPSAnZ3JlZW4nKSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKGJyZWFrcyA9IGMoJ3RydScsICdwYXJldCcsICdhZGFwdCcsICdyYW5kbycpLAogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCd0cnUnID0gJ1RydWUgQm91bmRhcnknLCAncGFyZXQnID0gJ1N0YXJ0aW5nIERhdGFzZXQnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnYWRhcHQnID0gJysgQWRhcHRpdmUgU2FtcGxpbmcnLCAncmFuZG8nID0gJysgUmFuZG9tIFNhbXBsaW5nJyksCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGMoJ3RydScgPSAnYmxhY2snLCAncGFyZXQnID0gJ3NreWJsdWUyJywgJ2FkYXB0JyA9ICdyZWQnLCAncmFuZG8nID0gJ2dyZWVuJykpICsKICBsYWJzKHggPSBleHByZXNzaW9uKCd4J1sxXSksIHggPSBleHByZXNzaW9uKCd4J1syXSksIGNvbG9yID0gJycsCiAgICAgICBzdWJ0aXRsZSA9IGV4cHJlc3Npb24oJ0NyaXRlcmlhOiBVdG9waWEgcG9pbnQgZGlzdGFuY2UgYW5kIGYnWzFdKicgUHJpb3JpdHkgPiA4MCUnKSkgKwogIHRoZW1lX2NsYXNzaWMoKSArIGd1aWRlcyhmaWxsID0gRkFMU0UpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYygwLjg1LCAwLjg1KSkgKwogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDUpLCBleHBhbmQgPSBjKDAsIDApKSArIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDUpLCBleHBhbmQgPSBjKDAsIDApKQoKCmBgYAoKClRoZSBlcnJvciByYXRlIGluIHRoZSBHUCBtb2RlbCBlc3RpbWF0ZSBzaG91bGQgYWNjb3VudCBmb3IgdGhlIGJ1aWx0LWluIHVuY2VydGFpbnR5IGluIHRoZSBtb2RlbC4KVGhlIHByb2JhYmlsaXR5IHRoYXQgdGhlIG1vZGVsIGdpdmVzIHRoZSB3cm9uZyByZXN1bHQgZ2l2ZW4gdGhlICQoeF8xLCB4XzIpJCBjb29yZGluYXRlcyBpcyB0aGUgcHJvYmFiaWxpdHkgdGhhdCBpdCBhY2NlcHRzIHRoZSBwb2ludCB3aGVuIGl0IHNob3VsZCByZWplY3QgaXQsIG9yIHZpY2UgdmVyc2EuClNpbmNlIGFsbCAkKHhfMSwgeF8yKSQgYXJlIGVxdWFsbHkgbGlrZWx5IGluIHRoaXMgbWF0aGVtYXRpY2FsIGZ1bmN0aW9uLCB0aGUgZXJyb3IgcmF0ZSBvZiB0aGUgR1AgbW9kZWwgaXMgdGhlIGF2ZXJhZ2Ugb2YgdGhlc2UgcHJvYmFiaWxpdGllcy4KVGhlcmUgc2hvdWxkIGJlIGFuIGV2aWRlbnQgZGVjcmVhc2UgaW4gdGhlIGVycm9yIHJhdGUgYmV0d2VlbiB0aGUgcHJlLSBhbmQgcG9zdC1yZWZpbmVtZW50IG1vZGVscy4KClRoZSBNb250ZSBDYXJsbyB1bmNlcnRhaW50eSBpbiB0aGUgZXJyb3IgcmF0ZSBpcyAkXHNxcnR7XGZyYWN7cCgxLXApfXtOfX0kLgoKVG8gc2F2ZSBvbiBjb21wdXRhdGlvbmFsIGNvc3QsIHVzaW5nIHRoZSBmaW5lIGdyaWQgYXMgYSBzdXJyb2dhdGUgZm9yIE1vbnRlIENhcmxvIHNhbXBsaW5nLgpUaGUgY2FsY3VsYXRpb24gc2VwYXJhdGVzIHRoZSB0ZXJtcyBpbnRvIHR5cGUgSSAoUFtzaG91bGQgcmVqZWN0IHwgYWNjZXB0ZWRdKSBhbmQgdHlwZSBJSSBlcnJvciAoUFtzaG91bGQgYWNjZXB0IHwgcmVqZWN0ZWRdKSBlcnJvciByYXRlcy4KVGhlc2UgY29uZGl0aW9uYWxzIGFyZSBjYWxjdWx0YWVkIGJ5IEJheWVzIHJ1bGUsCiRQW1h8WV0gPSBQW1gsIFldIFBbWF0gLyBQW1ldJAoKYGBge3IgR1A6IEVycm9yIHJhdGV9CmVycm9yLnJhdGUgPSBkYXRhLmZyYW1lKG1ldGhvZCA9ICdHUCcsIHNvdXJjZSA9IHJlcChjKCcwcGFyZXQnLCAnMWFkYXB0JywgJzJyYW5kbycpLCAzKSwKICAgICAgICAgICAgICAgICAgICAgICAgY3JpdGVyaWEgPSBjKHJlcCgnUGFyZXRvIERpc3RhbmNlJywgMyksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwKCdUaHJlc2hvbGQgQ3V0b2ZmJywgMyksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwKCdVdG9waWEgRGlzdGFuY2UnLCAzKSkpCmVycm9yLnJhdGUkY3JpdGVyaWEgPSBmYWN0b3IoZXJyb3IucmF0ZSRjcml0ZXJpYSwgbGV2ZWxzID0gYygiUGFyZXRvIERpc3RhbmNlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiVGhyZXNob2xkIEN1dG9mZiIsICJVdG9waWEgRGlzdGFuY2UiKSkKIyBUb3RhbCBlcnJvciByYXRlCmVycm9yLnJhdGUkcmF0ZSA9CiAgYyhtZWFuKGMoMSAtIGZpbHRlcihmaW5lLmdyaWQsIGRlbHRhID09IDEpJHByb2IuZGVsdGEucGFyZXQsIGZpbHRlcihmaW5lLmdyaWQsIGRlbHRhID09IDApJHByb2IuZGVsdGEucGFyZXQpKSwKICAgIG1lYW4oYygxIC0gZmlsdGVyKGZpbmUuZ3JpZCwgZGVsdGEgPT0gMSkkcHJvYi5kZWx0YS5hZGFwdCwgZmlsdGVyKGZpbmUuZ3JpZCwgZGVsdGEgPT0gMCkkcHJvYi5kZWx0YS5hZGFwdCkpLAogICAgbWVhbihjKDEgLSBmaWx0ZXIoZmluZS5ncmlkLCBkZWx0YSA9PSAxKSRwcm9iLmRlbHRhLnJhbmRvLCBmaWx0ZXIoZmluZS5ncmlkLCBkZWx0YSA9PSAwKSRwcm9iLmRlbHRhLnJhbmRvKSksCiAgICBtZWFuKGMoMSAtIGZpbHRlcihmaW5lLmdyaWQsIGN1dG9mID09IDEpJHByb2IuY3V0b2YucGFyZXQsIGZpbHRlcihmaW5lLmdyaWQsIGN1dG9mID09IDApJHByb2IuY3V0b2YucGFyZXQpKSwKICAgIG1lYW4oYygxIC0gZmlsdGVyKGZpbmUuZ3JpZCwgY3V0b2YgPT0gMSkkcHJvYi5jdXRvZi5hZGFwdCwgZmlsdGVyKGZpbmUuZ3JpZCwgY3V0b2YgPT0gMCkkcHJvYi5jdXRvZi5hZGFwdCkpLAogICAgbWVhbihjKDEgLSBmaWx0ZXIoZmluZS5ncmlkLCBjdXRvZiA9PSAxKSRwcm9iLmN1dG9mLnJhbmRvLCBmaWx0ZXIoZmluZS5ncmlkLCBjdXRvZiA9PSAwKSRwcm9iLmN1dG9mLnJhbmRvKSksCiAgICBtZWFuKGMoMSAtIGZpbHRlcihmaW5lLmdyaWQsIHJhZGFuID09IDEpJHByb2IucmFkYW4ucGFyZXQsIGZpbHRlcihmaW5lLmdyaWQsIHJhZGFuID09IDApJHByb2IucmFkYW4ucGFyZXQpKSwKICAgIG1lYW4oYygxIC0gZmlsdGVyKGZpbmUuZ3JpZCwgcmFkYW4gPT0gMSkkcHJvYi5yYWRhbi5hZGFwdCwgZmlsdGVyKGZpbmUuZ3JpZCwgcmFkYW4gPT0gMCkkcHJvYi5yYWRhbi5hZGFwdCkpLAogICAgbWVhbihjKDEgLSBmaWx0ZXIoZmluZS5ncmlkLCByYWRhbiA9PSAxKSRwcm9iLnJhZGFuLnJhbmRvLCBmaWx0ZXIoZmluZS5ncmlkLCByYWRhbiA9PSAwKSRwcm9iLnJhZGFuLnJhbmRvKSkpCmVycm9yLnJhdGUkZXJyID0gc3FydChlcnJvci5yYXRlJHJhdGUqKDEtZXJyb3IucmF0ZSRyYXRlKS9ucm93KGZpbmUuZ3JpZCkpCgojIFR5cGUgMiBlcnJvciA9IFBbc2hvdWxkIGFjY2VwdCB8IHJlamVjdGVkXQplcnJvci5yYXRlJHR5cDIgPQogIGMoc3VtKDEgLSBmaWx0ZXIoZmluZS5ncmlkLCBkZWx0YSA9PSAxKSRwcm9iLmRlbHRhLnBhcmV0KSpucm93KGZpbHRlcihmaW5lLmdyaWQsIGRlbHRhID09IDEpKS9ucm93KGZpbmUuZ3JpZCleMi8KICAgICAgbWVhbigxIC0gZmluZS5ncmlkJHByb2IuZGVsdGEucGFyZXQpLAogICAgc3VtKDEgLSBmaWx0ZXIoZmluZS5ncmlkLCBkZWx0YSA9PSAxKSRwcm9iLmRlbHRhLmFkYXB0KSpucm93KGZpbHRlcihmaW5lLmdyaWQsIGRlbHRhID09IDEpKS9ucm93KGZpbmUuZ3JpZCleMi8KICAgICAgbWVhbigxIC0gZmluZS5ncmlkJHByb2IuZGVsdGEuYWRhcHQpLAogICAgc3VtKDEgLSBmaWx0ZXIoZmluZS5ncmlkLCBkZWx0YSA9PSAxKSRwcm9iLmRlbHRhLnJhbmRvKSpucm93KGZpbHRlcihmaW5lLmdyaWQsIGRlbHRhID09IDEpKS9ucm93KGZpbmUuZ3JpZCleMi8KICAgICAgbWVhbigxIC0gZmluZS5ncmlkJHByb2IuZGVsdGEucmFuZG8pLAogICAgc3VtKDEgLSBmaWx0ZXIoZmluZS5ncmlkLCBjdXRvZiA9PSAxKSRwcm9iLmN1dG9mLnBhcmV0KSpucm93KGZpbHRlcihmaW5lLmdyaWQsIGN1dG9mID09IDEpKS9ucm93KGZpbmUuZ3JpZCleMi8KICAgICAgbWVhbigxIC0gZmluZS5ncmlkJHByb2IuY3V0b2YucGFyZXQpLAogICAgc3VtKDEgLSBmaWx0ZXIoZmluZS5ncmlkLCBjdXRvZiA9PSAxKSRwcm9iLmN1dG9mLmFkYXB0KSpucm93KGZpbHRlcihmaW5lLmdyaWQsIGN1dG9mID09IDEpKS9ucm93KGZpbmUuZ3JpZCleMi8KICAgICAgbWVhbigxIC0gZmluZS5ncmlkJHByb2IuY3V0b2YuYWRhcHQpLAogICAgc3VtKDEgLSBmaWx0ZXIoZmluZS5ncmlkLCBjdXRvZiA9PSAxKSRwcm9iLmN1dG9mLnJhbmRvKSpucm93KGZpbHRlcihmaW5lLmdyaWQsIGN1dG9mID09IDEpKS9ucm93KGZpbmUuZ3JpZCleMi8KICAgICAgbWVhbigxIC0gZmluZS5ncmlkJHByb2IuY3V0b2YucmFuZG8pLAogICAgc3VtKDEgLSBmaWx0ZXIoZmluZS5ncmlkLCByYWRhbiA9PSAxKSRwcm9iLnJhZGFuLnBhcmV0KSpucm93KGZpbHRlcihmaW5lLmdyaWQsIHJhZGFuID09IDEpKS9ucm93KGZpbmUuZ3JpZCleMi8KICAgICAgbWVhbigxIC0gZmluZS5ncmlkJHByb2IucmFkYW4ucGFyZXQpLAogICAgc3VtKDEgLSBmaWx0ZXIoZmluZS5ncmlkLCByYWRhbiA9PSAxKSRwcm9iLnJhZGFuLmFkYXB0KSpucm93KGZpbHRlcihmaW5lLmdyaWQsIHJhZGFuID09IDEpKS9ucm93KGZpbmUuZ3JpZCleMi8KICAgICAgbWVhbigxIC0gZmluZS5ncmlkJHByb2IucmFkYW4uYWRhcHQpLAogICAgc3VtKDEgLSBmaWx0ZXIoZmluZS5ncmlkLCByYWRhbiA9PSAxKSRwcm9iLnJhZGFuLnJhbmRvKSpucm93KGZpbHRlcihmaW5lLmdyaWQsIHJhZGFuID09IDEpKS9ucm93KGZpbmUuZ3JpZCleMi8KICAgICAgbWVhbigxIC0gZmluZS5ncmlkJHByb2IucmFkYW4ucmFuZG8pKQplcnJvci5yYXRlJHR5cDIuZXJyID0gc3FydChlcnJvci5yYXRlJHR5cDIqKDEtZXJyb3IucmF0ZSR0eXAyKSAvIG5yb3coZmluZS5ncmlkKSkKICAgICAgICAgICAgICAgICAgICAgICAjIGMocmVwKG5yb3coZmlsdGVyKGZpbmUuZ3JpZCwgZGVsdGEgPT0gMCkpLCAzKSwKICAgICAgICAgICAgICAgICAgICAgICAjICAgcmVwKG5yb3coZmlsdGVyKGZpbmUuZ3JpZCwgY3V0b2YgPT0gMCkpLCAzKSwKICAgICAgICAgICAgICAgICAgICAgICAjICAgcmVwKG5yb3coZmlsdGVyKGZpbmUuZ3JpZCwgcmFkYW4gPT0gMCkpLCAzKSkpCgojIFR5cGUgMSBlcnJvciA9IFBbc2hvdWxkIHJlamVjdCB8IGFjY2VwdGVkXQplcnJvci5yYXRlJHR5cDEgPQogIGMoc3VtKGZpbHRlcihmaW5lLmdyaWQsIGRlbHRhID09IDApJHByb2IuZGVsdGEucGFyZXQpKm5yb3coZmlsdGVyKGZpbmUuZ3JpZCwgZGVsdGEgPT0gMCkpL25yb3coZmluZS5ncmlkKV4yLwogICAgICBtZWFuKGZpbmUuZ3JpZCRwcm9iLmRlbHRhLnBhcmV0KSwKICAgIHN1bShmaWx0ZXIoZmluZS5ncmlkLCBkZWx0YSA9PSAwKSRwcm9iLmRlbHRhLmFkYXB0KSpucm93KGZpbHRlcihmaW5lLmdyaWQsIGRlbHRhID09IDApKS9ucm93KGZpbmUuZ3JpZCleMi8KICAgICAgbWVhbihmaW5lLmdyaWQkcHJvYi5kZWx0YS5hZGFwdCksCiAgICBzdW0oZmlsdGVyKGZpbmUuZ3JpZCwgZGVsdGEgPT0gMCkkcHJvYi5kZWx0YS5yYW5kbykqbnJvdyhmaWx0ZXIoZmluZS5ncmlkLCBkZWx0YSA9PSAwKSkvbnJvdyhmaW5lLmdyaWQpXjIvCiAgICAgIG1lYW4oZmluZS5ncmlkJHByb2IuZGVsdGEucmFuZG8pLAogICAgc3VtKGZpbHRlcihmaW5lLmdyaWQsIGN1dG9mID09IDApJHByb2IuY3V0b2YucGFyZXQpKm5yb3coZmlsdGVyKGZpbmUuZ3JpZCwgY3V0b2YgPT0gMCkpL25yb3coZmluZS5ncmlkKV4yLwogICAgICBtZWFuKGZpbmUuZ3JpZCRwcm9iLmN1dG9mLnBhcmV0KSwKICAgIHN1bShmaWx0ZXIoZmluZS5ncmlkLCBjdXRvZiA9PSAwKSRwcm9iLmN1dG9mLmFkYXB0KSpucm93KGZpbHRlcihmaW5lLmdyaWQsIGN1dG9mID09IDApKS9ucm93KGZpbmUuZ3JpZCleMi8KICAgICAgbWVhbihmaW5lLmdyaWQkcHJvYi5jdXRvZi5hZGFwdCksCiAgICBzdW0oZmlsdGVyKGZpbmUuZ3JpZCwgY3V0b2YgPT0gMCkkcHJvYi5jdXRvZi5yYW5kbykqbnJvdyhmaWx0ZXIoZmluZS5ncmlkLCBjdXRvZiA9PSAwKSkvbnJvdyhmaW5lLmdyaWQpXjIvCiAgICAgIG1lYW4oZmluZS5ncmlkJHByb2IuY3V0b2YucmFuZG8pLAogICAgc3VtKGZpbHRlcihmaW5lLmdyaWQsIHJhZGFuID09IDApJHByb2IucmFkYW4ucGFyZXQpKm5yb3coZmlsdGVyKGZpbmUuZ3JpZCwgcmFkYW4gPT0gMCkpL25yb3coZmluZS5ncmlkKV4yLwogICAgICBtZWFuKGZpbmUuZ3JpZCRwcm9iLnJhZGFuLnBhcmV0KSwKICAgIHN1bShmaWx0ZXIoZmluZS5ncmlkLCByYWRhbiA9PSAwKSRwcm9iLnJhZGFuLmFkYXB0KSpucm93KGZpbHRlcihmaW5lLmdyaWQsIHJhZGFuID09IDApKS9ucm93KGZpbmUuZ3JpZCleMi8KICAgICAgbWVhbihmaW5lLmdyaWQkcHJvYi5yYWRhbi5hZGFwdCksCiAgICBzdW0oZmlsdGVyKGZpbmUuZ3JpZCwgcmFkYW4gPT0gMCkkcHJvYi5yYWRhbi5yYW5kbykqbnJvdyhmaWx0ZXIoZmluZS5ncmlkLCByYWRhbiA9PSAwKSkvbnJvdyhmaW5lLmdyaWQpXjIvCiAgICAgIG1lYW4oZmluZS5ncmlkJHByb2IucmFkYW4ucmFuZG8pKQplcnJvci5yYXRlJHR5cDEuZXJyID0gc3FydChlcnJvci5yYXRlJHR5cDEqKDEtZXJyb3IucmF0ZSR0eXAxKSAvIG5yb3coZmluZS5ncmlkKSkKICAgICAgICAgICAgICAgICAgICAgICAjIGMocmVwKG5yb3coZmlsdGVyKGZpbmUuZ3JpZCwgZGVsdGEgPT0gMCkpLCAzKSwKICAgICAgICAgICAgICAgICAgICAgICAjICAgcmVwKG5yb3coZmlsdGVyKGZpbmUuZ3JpZCwgY3V0b2YgPT0gMCkpLCAzKSwKICAgICAgICAgICAgICAgICAgICAgICAjICAgcmVwKG5yb3coZmlsdGVyKGZpbmUuZ3JpZCwgcmFkYW4gPT0gMCkpLCAzKSkpCmVycm9yLnJhdGUKCmcudG90ID0gZ2dwbG90KGVycm9yLnJhdGUpICsKICBnZW9tX2NvbChtYXBwaW5nID0gYWVzKHggPSBzb3VyY2UsIHkgPSByYXRlLCBmaWxsID0gc291cmNlKSkgKwogIGdlb21fZXJyb3JiYXIobWFwcGluZyA9IGFlcyh4ID0gc291cmNlLCB5ID0gcmF0ZSwgeW1pbiA9IHJhdGUgLSBlcnIsIHltYXggPSByYXRlICsgZXJyKSwgd2lkdGggPSAwLjUpICsKICBmYWNldF93cmFwKH5jcml0ZXJpYSwgc2NhbGVzID0gJ2ZyZWUnKSArCiAgc2NhbGVfeF9kaXNjcmV0ZShicmVha3MgPSBjKCksIGxhYmVscyA9IGMoKSkgKwogIGxhYnMoeCA9ICcnLCB5ID0gJ1RvdGFsXG5FcnJvciBSYXRlJykgKwogIHNjYWxlX2ZpbGxfbWFudWFsKGxhYmVscyA9IGMoJzBwYXJldCcgPSAnU3RhcnRpbmcgRGF0YXNldCcsICcxYWRhcHQnID0gJysgQWRhcHRpdmUgU2FtcGxpbmcnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJzJyYW5kbycgPSAnKyBSYW5kb20gU2FtcGxpbmcnKSwgbmFtZSA9ICcnLAogICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYygnMHBhcmV0JyA9ICdza3libHVlMicsICcxYWRhcHQnID0gJ3JlZCcsICcycmFuZG8nID0gJ2dyZWVuJykpICsKICB0aGVtZV9jbGFzc2ljKCkgKyBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gZXhwYW5zaW9uKG11bHQgPSBjKDAsIC4xKSkpCmcudHkxID0gZ2dwbG90KGVycm9yLnJhdGUpICsKICBnZW9tX2NvbChtYXBwaW5nID0gYWVzKHggPSBzb3VyY2UsIHkgPSB0eXAxLCBmaWxsID0gc291cmNlKSkgKwogIGdlb21fZXJyb3JiYXIobWFwcGluZyA9IGFlcyh4ID0gc291cmNlLCB5ID0gdHlwMSwgeW1pbiA9IHR5cDEgLSB0eXAxLmVyciwgeW1heCA9IHR5cDEgKyB0eXAxLmVyciksIHdpZHRoID0gMC41KSArCiAgZmFjZXRfd3JhcCh+Y3JpdGVyaWEsIHNjYWxlcyA9ICdmcmVlJykgKwogIHNjYWxlX3hfZGlzY3JldGUoYnJlYWtzID0gYygpLCBsYWJlbHMgPSBjKCkpICsKICBsYWJzKHggPSAnJywgeSA9ICdGYWxzZSBQb3NpdGl2ZVxuRXJyb3IgUmF0ZScpICsKICBzY2FsZV9maWxsX21hbnVhbChsYWJlbHMgPSBjKCcwcGFyZXQnID0gJ1N0YXJ0aW5nIERhdGFzZXQnLCAnMWFkYXB0JyA9ICcrIEFkYXB0aXZlIFNhbXBsaW5nJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICcycmFuZG8nID0gJysgUmFuZG9tIFNhbXBsaW5nJyksIG5hbWUgPSAnJywKICAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGMoJzBwYXJldCcgPSAnc2t5Ymx1ZTInLCAnMWFkYXB0JyA9ICdyZWQnLCAnMnJhbmRvJyA9ICdncmVlbicpKSArCiAgdGhlbWVfY2xhc3NpYygpICsgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGV4cGFuc2lvbihtdWx0ID0gYygwLCAuMSkpKQpnLnR5MiA9IGdncGxvdChlcnJvci5yYXRlKSArCiAgZ2VvbV9jb2wobWFwcGluZyA9IGFlcyh4ID0gc291cmNlLCB5ID0gdHlwMiwgZmlsbCA9IHNvdXJjZSkpICsKICBnZW9tX2Vycm9yYmFyKG1hcHBpbmcgPSBhZXMoeCA9IHNvdXJjZSwgeSA9IHR5cDIsIHltaW4gPSB0eXAyIC0gdHlwMi5lcnIsIHltYXggPSB0eXAyICsgdHlwMi5lcnIpLCB3aWR0aCA9IDAuNSkgKwogIGZhY2V0X3dyYXAofmNyaXRlcmlhLCBzY2FsZXMgPSAnZnJlZScpICsKICBzY2FsZV94X2Rpc2NyZXRlKGJyZWFrcyA9IGMoKSwgbGFiZWxzID0gYygpKSArCiAgbGFicyh4ID0gJycsIHkgPSAnRmFsc2UgTmVnYXRpdmVcbkVycm9yIFJhdGUnKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobGFiZWxzID0gYygnMHBhcmV0JyA9ICdTdGFydGluZyBEYXRhc2V0JywgJzFhZGFwdCcgPSAnKyBBZGFwdGl2ZSBTYW1wbGluZycsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnMnJhbmRvJyA9ICcrIFJhbmRvbSBTYW1wbGluZycpLCBuYW1lID0gJycsCiAgICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBjKCcwcGFyZXQnID0gJ3NreWJsdWUyJywgJzFhZGFwdCcgPSAncmVkJywgJzJyYW5kbycgPSAnZ3JlZW4nKSkgKwogIHRoZW1lX2NsYXNzaWMoKSArIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBleHBhbnNpb24obXVsdCA9IGMoMCwgLjEpKSkKCihnLnRvdCArIGd1aWRlcyhmaWxsID0gRkFMU0UpKSAvIGcudHkxIC8gKGcudHkyICsgZ3VpZGVzKGZpbGwgPSBGQUxTRSkpCgp3cml0ZS5jc3YoZXJyb3IucmF0ZSwgJ0Vycm9yUmF0ZXMuY3N2Jywgcm93Lm5hbWVzID0gRikKcm0oZy50b3QsIGcudHkxLCBnLnR5MikKYGBgCgpBIGNsb3NlciBpbnNwZWN0aW9uIG9mIGVhY2ggR1AtZGlzY292ZXJlZCBib3VuZGFyeSBjb21wYXJlZCB0byB0aGUgYWN0dWFsIGJvdW5kYXJ5IGJhc2VkIG9uIGZpbmUtcmVzb2x1dGlvbiBmdW5jdGlvbiBldmFsdWF0aW9uLgoKYGBge3IgR1AgTWFyZ2luYWxzIENvbXBhcmlzb259CiMgU2luZ2xlIHZhcmlhYmxlIGNvbmRpdGlvbmFscyBjb21wYXJlZCB0byB0aGUgZXhwZWN0ZWQgY29uZGl0aW9uYWxzIGJ5IGludGVncmF0aW9uCkluZmVyLnBsdCA9IHJlYWQuY3N2KCcuLi9FeF9RdWFydGljL01hcmdpbmFsc19kZWx0YS5jc3YnKQpnZ3Bsb3QoKSArCiAgZ2VvbV9wYXRoKGRhdGEgPSBmaWx0ZXIoSW5mZXIucGx0LCBjYXQgPT0gJ3RydScpLCBtYXBwaW5nID0gYWVzKHggPSB4LCB5ID0gcHJvYiAtIHBzZCwgY29sb3IgPSBjYXQpLCBsaW5ldHlwZSA9IDIpICsKICBnZW9tX3BhdGgoZGF0YSA9IGZpbHRlcihJbmZlci5wbHQsIGNhdCA9PSAndHJ1JyksIG1hcHBpbmcgPSBhZXMoeCA9IHgsIHkgPSBwcm9iICsgcHNkLCBjb2xvciA9IGNhdCksIGxpbmV0eXBlID0gMikgKwogIGdlb21fcGF0aChkYXRhID0gZmlsdGVyKEluZmVyLnBsdCwgY2F0ICE9ICd0cnUnKSwgbWFwcGluZyA9IGFlcyh4ID0geCwgeSA9IHByb2IsIGNvbG9yID0gY2F0KSkgKwogIGZhY2V0X3dyYXAodmFyfi4sIG5yb3cgPSAyKSArIAogIHNjYWxlX2NvbG9yX21hbnVhbChsYWJlbHMgPSBjKCd0cnUnID0gJ0V4cGVjdGVkIE1hcmdpbmFsJywgJ3N0YXJ0JyA9ICdTdGFydGluZyBEYXRhc2V0JywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnYWRhcHQnID0gJysgQWRhcHRpdmUgU2FtcGxpbmcnLCAncmFuZG8nID0gJysgUmFuZG9tIFNhbXBsaW5nJyksCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGMoJ3RydScgPSAnYmxhY2snLCAnc3RhcnQnID0gJ3NreWJsdWUyJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnYWRhcHQnID0gJ3JlZCcsICdyYW5kbycgPSAnZ3JlZW4nKSwKICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYygndHJ1JywgJ3N0YXJ0JywgJ2FkYXB0JywgJ3JhbmRvJykpICsKICBndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChsaW5ldHlwZSA9IGMoMiwgMSwgMSwgMSkpKSkgKwogIGxhYnMoeCA9ICcnLCB5ID0gJ1Byb2JhYmlsaXR5IG9mIEFjY2VwdGFuY2UnLCBzdWJ0aXRsZSA9IGV4cHJlc3Npb24oJ1BhcmV0byBEaXN0YW5jZScpLCBjb2xvciA9ICcnKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMC4wNSkpICsgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCkpICsKICB0aGVtZV9idygpCgpJbmZlci5wbHQgPSByZWFkLmNzdignLi4vRXhfUXVhcnRpYy9NYXJnaW5hbHNfY3V0b2YuY3N2JykKZ2dwbG90KCkgKwogIGdlb21fcGF0aChkYXRhID0gZmlsdGVyKEluZmVyLnBsdCwgY2F0ID09ICd0cnUnKSwgbWFwcGluZyA9IGFlcyh4ID0geCwgeSA9IHByb2IgLSBwc2QsIGNvbG9yID0gY2F0KSwgbGluZXR5cGUgPSAyKSArCiAgZ2VvbV9wYXRoKGRhdGEgPSBmaWx0ZXIoSW5mZXIucGx0LCBjYXQgPT0gJ3RydScpLCBtYXBwaW5nID0gYWVzKHggPSB4LCB5ID0gcHJvYiArIHBzZCwgY29sb3IgPSBjYXQpLCBsaW5ldHlwZSA9IDIpICsKICBnZW9tX3BhdGgoZGF0YSA9IGZpbHRlcihJbmZlci5wbHQsIGNhdCAhPSAndHJ1JyksIG1hcHBpbmcgPSBhZXMoeCA9IHgsIHkgPSBwcm9iLCBjb2xvciA9IGNhdCkpICsKICBmYWNldF93cmFwKHZhcn4uLCBucm93ID0gMikgKyAKICBzY2FsZV9jb2xvcl9tYW51YWwobGFiZWxzID0gYygndHJ1JyA9ICdFeHBlY3RlZCBNYXJnaW5hbCcsICdzdGFydCcgPSAnU3RhcnRpbmcgRGF0YXNldCcsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ2FkYXB0JyA9ICcrIEFkYXB0aXZlIFNhbXBsaW5nJywgJ3JhbmRvJyA9ICcrIFJhbmRvbSBTYW1wbGluZycpLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBjKCd0cnUnID0gJ2JsYWNrJywgJ3N0YXJ0JyA9ICdza3libHVlMicsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ2FkYXB0JyA9ICdyZWQnLCAncmFuZG8nID0gJ2dyZWVuJyksCiAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IGMoJ3RydScsICdzdGFydCcsICdhZGFwdCcsICdyYW5kbycpKSArCiAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3QobGluZXR5cGUgPSBjKDIsIDEsIDEsIDEpKSkpICsKICBsYWJzKHggPSAnJywgeSA9ICdQcm9iYWJpbGl0eSBvZiBBY2NlcHRhbmNlJywgc3VidGl0bGUgPSBleHByZXNzaW9uKCdDdXRvZmYgVGhyZXNob2xkcycpLCBjb2xvciA9ICcnKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMC4wNSkpICsgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCkpICsKICB0aGVtZV9idygpCgpJbmZlci5wbHQgPSByZWFkLmNzdignLi4vRXhfUXVhcnRpYy9NYXJnaW5hbHNfcmFkYW4uY3N2JykKZ2dwbG90KCkgKwogIGdlb21fcGF0aChkYXRhID0gZmlsdGVyKEluZmVyLnBsdCwgY2F0ID09ICd0cnUnKSwgbWFwcGluZyA9IGFlcyh4ID0geCwgeSA9IHByb2IgLSBwc2QsIGNvbG9yID0gY2F0KSwgbGluZXR5cGUgPSAyKSArCiAgZ2VvbV9wYXRoKGRhdGEgPSBmaWx0ZXIoSW5mZXIucGx0LCBjYXQgPT0gJ3RydScpLCBtYXBwaW5nID0gYWVzKHggPSB4LCB5ID0gcHJvYiArIHBzZCwgY29sb3IgPSBjYXQpLCBsaW5ldHlwZSA9IDIpICsKICBnZW9tX3BhdGgoZGF0YSA9IGZpbHRlcihJbmZlci5wbHQsIGNhdCAhPSAndHJ1JyksIG1hcHBpbmcgPSBhZXMoeCA9IHgsIHkgPSBwcm9iLCBjb2xvciA9IGNhdCkpICsKICBmYWNldF93cmFwKHZhcn4uLCBucm93ID0gMiwgbGFiZWxsZXIgPSBsYWJlbF9wYXJzZWQpICsgCiAgc2NhbGVfY29sb3JfbWFudWFsKGxhYmVscyA9IGMoJ3RydScgPSAnRXhwZWN0ZWQgTWFyZ2luYWwnLCAnc3RhcnQnID0gJ1N0YXJ0aW5nIERhdGFzZXQnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdhZGFwdCcgPSAnKyBBZGFwdGl2ZSBTYW1wbGluZycsICdyYW5kbycgPSAnKyBSYW5kb20gU2FtcGxpbmcnKSwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYygndHJ1JyA9ICdibGFjaycsICdzdGFydCcgPSAnc2t5Ymx1ZTInLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdhZGFwdCcgPSAncmVkJywgJ3JhbmRvJyA9ICdncmVlbicpLAogICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBjKCd0cnUnLCAnc3RhcnQnLCAnYWRhcHQnLCAncmFuZG8nKSkgKwogIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KGxpbmV0eXBlID0gYygyLCAxLCAxLCAxKSkpKSArCiAgbGFicyh4ID0gJycsIHkgPSAnUHJvYmFiaWxpdHkgb2YgQWNjZXB0YW5jZScsIHN1YnRpdGxlID0gZXhwcmVzc2lvbignVXRvcGlhIERpc3RhbmNlICsgUHJpb3JpdHknKSwgY29sb3IgPSAnJykgKwogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDAuMDUpKSArIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApKSArCiAgdGhlbWVfYncoKQoKCmBgYAoKQWx0ZXJuYXRpdmUgTWV0cmljOiBBY2N1cmFjeSBhcyBuIHNhbXBsZXMgYXJlIGNvbGxlY3RlZAoKYGBge3J9Cm1vZC5lcnIubiA9IGZ1bmN0aW9uKGRhdGEuZGlzdCwgZGF0YS5mLCBkYXRhLnJhZCwgdGVzdC52ZWN0b3IpewogICMgR2l2ZW4gYSB0cmFpbmluZyBhbmQgdGVzdGluZyBkYXRhc2V0LCBnaXZlcyB0aGUgZXJyb3Igb2YgdGhlIG1vZGVsCiAgIyBJbnRlbnQgaXMgdG8gdXNlIG4gc2FtcGxlcyBpbiB0aGUgdHJhaW5pbmcgZGF0YXNldCB0byBjb21wYXJlIGVmZmVjdCBvZiBpbmNyZWFzaW5nCiAgIyB0aGUgc2FtcGxlIHNpemUgb2YgZGlmZmVyZW50IHNhbXBsaW5nIGFwcHJvYWNoZXMKICAKICAjIEdpdmVuIGRhdGEgb2Ygc2l6ZSBuLCBjcmVhdGUgYWxsIG9mIHRoZSByZWxldmFudCBtb2RlbHMKICBtb2QuZGlzdCA9IGZpbGwuc2FtcGxlLm1vZChHUGFyLmRhdGEgPSBkYXRhLmRpc3QsIGlucHV0Lm5hbWUgPSBjKCd4MScsICd4MicpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dHB1dC5uYW1lID0gJ2Rpc3QnKQogIG1vZC5mMSA9IGZpbGwuc2FtcGxlLm1vZChHUGFyLmRhdGEgPSBkYXRhLmYsIGlucHV0Lm5hbWUgPSBjKCd4MScsICd4MicpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dHB1dC5uYW1lID0gJ2YxLm5vcm0nKQogIG1vZC5mMiA9IGZpbGwuc2FtcGxlLm1vZChHUGFyLmRhdGEgPSBkYXRhLmYsIGlucHV0Lm5hbWUgPSBjKCd4MScsICd4MicpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dHB1dC5uYW1lID0gJ2YyLm5vcm0nKQogIG1vZC5yYWQgPSBmaWxsLnNhbXBsZS5tb2QoR1Bhci5kYXRhID0gZGF0YS5yYWQsIGlucHV0Lm5hbWUgPSBjKCd4MScsICd4MicpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dHB1dC5uYW1lID0gJ3JhZCcpCiAgbW9kLmFuZyA9IGZpbGwuc2FtcGxlLm1vZChHUGFyLmRhdGEgPSBkYXRhLnJhZCwgaW5wdXQubmFtZSA9IGMoJ3gxJywgJ3gyJyksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgb3V0cHV0Lm5hbWUgPSAndGhldGEnKQogIAogICMgQ2FsY3VsYXRlIHRoZSBwcmVkaWN0aW9ucwogIHJlcy5kaXN0ID0gcHJlZGljdChvYmplY3QgPSBtb2QuZGlzdCxuZXdkYXRhID0gZmluZS5ncmlkWywgYygneDEnLCAneDInKV0sIHR5cGUgPSAnVUsnKQogIHJlcy5mMSAgID0gcHJlZGljdChvYmplY3QgPSBtb2QuZjEsICBuZXdkYXRhID0gZmluZS5ncmlkWywgYygneDEnLCAneDInKV0sIHR5cGUgPSAiVUsiKQogIHJlcy5mMiAgID0gcHJlZGljdChvYmplY3QgPSBtb2QuZjIsICBuZXdkYXRhID0gZmluZS5ncmlkWywgYygneDEnLCAneDInKV0sIHR5cGUgPSAiVUsiKQogIHJlcy5yYWQgID0gcHJlZGljdChvYmplY3QgPSBtb2QucmFkLCBuZXdkYXRhID0gZmluZS5ncmlkWywgYygneDEnLCAneDInKV0sIHR5cGUgPSAiVUsiKQogIHJlcy5hbmcgID0gcHJlZGljdChvYmplY3QgPSBtb2QuYW5nLCBuZXdkYXRhID0gZmluZS5ncmlkWywgYygneDEnLCAneDInKV0sIHR5cGUgPSAiVUsiKQoKICAjIENhbGN1bGF0ZSB0aGUgZXJyb3IgbGlrZWxpaG9vZHMKICB0ZXN0LnZlY3RvciRwcm9iLmRlbHRhID0gcG5vcm0ocSA9IDAsIG1lYW4gPSByZXMuZGlzdCRtZWFuIC0gMSwgc2QgPSByZXMuZGlzdCRzZCkKICB0ZXN0LnZlY3RvciRwcm9iLmN1dG9mID0gcG5vcm0ocSA9IDAsIG1lYW4gPSByZXMuZjEkbWVhbiAtIDEsIHNkID0gcmVzLmYxJHNkKSAqIAogICAgcG5vcm0ocSA9IDAsIG1lYW4gPSByZXMuZjIkbWVhbiAtIDEsIHNkID0gcmVzLmYyJHNkKQogIHRlc3QudmVjdG9yJHByb2IucmFkYW4gPSBwbm9ybShxID0gMCwgbWVhbiA9IHJlcy5yYWQkbWVhbiAtIDEsIHNkID0gcmVzLnJhZCRzZCkgKiAKICAgICgxIC0gcG5vcm0ocSA9IDAsIG1lYW4gPSByZXMuYW5nJG1lYW4gLSAyMCwgc2QgPSByZXMuYW5nJHNkKSkKICAKICAjIENhbGN1bGF0ZSB0b3RhbCwgZmFsc2UgcG9zaXRpdmUsIGFuZCBmYWxzZSBuZWdhdGl2ZSBlcnJvciByYXRlcwogIHRvdC5lcnIgPSBjKG1lYW4oYygxIC0gZHBseXI6OmZpbHRlcih0ZXN0LnZlY3RvciwgZGVsdGEgPT0gMSkkcHJvYi5kZWx0YSwgCiAgICAgICAgICAgICAgICAgICAgICAgZHBseXI6OmZpbHRlcih0ZXN0LnZlY3RvciwgZGVsdGEgPT0gMCkkcHJvYi5kZWx0YSkpLAogICAgICAgICAgICBtZWFuKGMoMSAtIGRwbHlyOjpmaWx0ZXIodGVzdC52ZWN0b3IsIGN1dG9mID09IDEpJHByb2IuY3V0b2YsIAogICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpmaWx0ZXIodGVzdC52ZWN0b3IsIGN1dG9mID09IDApJHByb2IuY3V0b2YpKSwKICAgICAgICAgICAgbWVhbihjKDEgLSBkcGx5cjo6ZmlsdGVyKHRlc3QudmVjdG9yLCByYWRhbiA9PSAxKSRwcm9iLnJhZGFuLCAKICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6ZmlsdGVyKHRlc3QudmVjdG9yLCByYWRhbiA9PSAwKSRwcm9iLnJhZGFuKSkpCiAgIyB0b3QudW5jID0gc3FydCh0b3QuZXJyKigxLXRvdC5lcnIpL25yb3codGVzdC52ZWN0b3IpKQogIAogIG5lZy5lcnIgPSAKICAgIGMoc3VtKDEgLSBkcGx5cjo6ZmlsdGVyKHRlc3QudmVjdG9yLCBkZWx0YSA9PSAxKSRwcm9iLmRlbHRhKSoKICAgICAgICBucm93KGRwbHlyOjpmaWx0ZXIodGVzdC52ZWN0b3IsIGRlbHRhID09IDEpKS9ucm93KHRlc3QudmVjdG9yKV4yLwogICAgICAgIG1lYW4oMSAtIHRlc3QudmVjdG9yJHByb2IuZGVsdGEpLAogICAgICAKICAgICAgc3VtKDEgLSBkcGx5cjo6ZmlsdGVyKHRlc3QudmVjdG9yLCBjdXRvZiA9PSAxKSRwcm9iLmN1dG9mKSoKICAgICAgICBucm93KGRwbHlyOjpmaWx0ZXIodGVzdC52ZWN0b3IsIGN1dG9mID09IDEpKS9ucm93KHRlc3QudmVjdG9yKV4yLwogICAgICAgIG1lYW4oMSAtIHRlc3QudmVjdG9yJHByb2IuY3V0b2YpLAogICAgICAKICAgICAgc3VtKDEgLSBkcGx5cjo6ZmlsdGVyKHRlc3QudmVjdG9yLCByYWRhbiA9PSAxKSRwcm9iLnJhZGFuKSoKICAgICAgICBucm93KGRwbHlyOjpmaWx0ZXIodGVzdC52ZWN0b3IsIHJhZGFuID09IDEpKS9ucm93KHRlc3QudmVjdG9yKV4yLwogICAgICAgIG1lYW4oMSAtIHRlc3QudmVjdG9yJHByb2IucmFkYW4pKQogICMgbmVnLnVuYyA9IHNxcnQobmVnLmVyciooMS1uZWcuZXJyKSAvIG5yb3codGVzdC52ZWN0b3IpKQogIAogIHBvcy5lcnIgPSAKICAgIGMoc3VtKGRwbHlyOjpmaWx0ZXIodGVzdC52ZWN0b3IsIGRlbHRhID09IDApJHByb2IuZGVsdGEpKgogICAgICAgIG5yb3coZHBseXI6OmZpbHRlcih0ZXN0LnZlY3RvciwgZGVsdGEgPT0gMCkpL25yb3codGVzdC52ZWN0b3IpXjIvCiAgICAgICAgbWVhbih0ZXN0LnZlY3RvciRwcm9iLmRlbHRhKSwKICAgICAgCiAgICAgIHN1bShkcGx5cjo6ZmlsdGVyKHRlc3QudmVjdG9yLCBjdXRvZiA9PSAwKSRwcm9iLmN1dG9mKSoKICAgICAgICBucm93KGRwbHlyOjpmaWx0ZXIodGVzdC52ZWN0b3IsIGN1dG9mID09IDApKS9ucm93KHRlc3QudmVjdG9yKV4yLwogICAgICAgIG1lYW4odGVzdC52ZWN0b3IkcHJvYi5jdXRvZiksCiAgICAgIAogICAgICBzdW0oZHBseXI6OmZpbHRlcih0ZXN0LnZlY3RvciwgcmFkYW4gPT0gMCkkcHJvYi5yYWRhbikqCiAgICAgICAgbnJvdyhkcGx5cjo6ZmlsdGVyKHRlc3QudmVjdG9yLCByYWRhbiA9PSAwKSkvbnJvdyh0ZXN0LnZlY3RvcileMi8KICAgICAgICBtZWFuKHRlc3QudmVjdG9yJHByb2IucmFkYW4pKQogICMgcG9zLnVuYyA9IHNxcnQocG9zLmVyciooMS1wb3MuZXJyKSAvIG5yb3codGVzdC52ZWN0b3IpKQogIAogICMgRm9yIHRoZSBwdXJwb3NlcyBvZiBkYXRhIHZpc3VhbGl6YXRpb24gYW5kIGJvdW5kaW5nIG9mIGEgcHJvYmFiaWxpdHksIGZyYW1lIAogICMgdGhlIHVuY2VydGFpbnR5IGFzIHRoZSB1cHBlciBhbmQgbG93ZXIgYm91bmQgKCsvLSBTRCkgc3VjaCB0aGF0IGlzIGl0IGFsd2F5cwogICMgYm91bmRlZCBiZXR3ZWVuIDAgYW5kIDEKICAjIHRvdC5taW4gPSB0b3QuZXJyIC0gdG90LnVuYzsgdG90Lm1heCA9IHRvdC5lcnIgKyB0b3QudW5jCiAgIyBwb3MubWluID0gcG9zLmVyciAtIHBvcy51bmM7IHBvcy5tYXggPSBwb3MuZXJyICsgcG9zLnVuYwogICMgbmVnLm1pbiA9IG5lZy5lcnIgLSBuZWcudW5jOyBuZWcubWF4ID0gbmVnLmVyciArIG5lZy51bmMKICAjIHRvdC5taW5bdG90Lm1pbiA8IDBdID0gMDsgdG90Lm1heFt0b3QubWF4ID4gMV0gPSAxCiAgIyBwb3MubWluW3Bvcy5taW4gPCAwXSA9IDA7IHBvcy5tYXhbcG9zLm1heCA+IDFdID0gMQogICMgbmVnLm1pbltuZWcubWluIDwgMF0gPSAwOyBuZWcubWF4W25lZy5tYXggPiAxXSA9IDEKICAKICByZXR1cm4oZGF0YS5mcmFtZShlcnIgPSBjKHRvdC5lcnIsIG5lZy5lcnIsIHBvcy5lcnIpLCAKICAgICAgICAgICAgIyB1bmMubWluID0gYyh0b3QubWluLCBuZWcubWluLCBwb3MubWluKSwgCiAgICAgICAgICAgICMgdW5jLm1heCA9IGModG90Lm1heCwgbmVnLm1heCwgcG9zLm1heCksIAogICAgICAgICAgICBjbGFzcyA9IGMoJ1BhcmV0byBEaXN0YW5jZScsICdUaHJlc2hvbGQgQ3V0b2ZmJywgJ1V0b3BpYSBEaXN0YW5jZScpLAogICAgICAgICAgICB0eXAgPSBjKHJlcCgnVG90YWwnLCAzKSwgcmVwKCdGYWxzZSBOZWdhdGl2ZScsIDMpLCAKICAgICAgICAgICAgICAgICAgICByZXAoJ0ZhbHNlIFBvc2l0aXZlJywgMykpKSkKfQoKdGVzdC52ZWMgPSBmaW5lLmdyaWRbLCBuYW1lcyhmaW5lLmdyaWQpICVpbiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgYygneDEnLCAneDInLCAnZGVsdGEnLCAnY3V0b2YnLCAncmFkYW4nKV0KCiMgVXNpbmcgdGhlIGZ1bGwgcmFuZG9tIGRhdGFzZXQgdG8gZ2V0IGl0cyB2YXJpYW5jZQpkYXRhLnJhbmRvID0gcmVhZC5jc3YoZmlsZSA9ICdHUGFyX1JhbmRvbS5jc3YnKQoKbnNhbXAgPSBzZXEoZnJvbSA9IG5yb3coZGF0YS5wYXJldCkgKyAxLCB0byA9IG5yb3coZGF0YS5kZWx0YSksIGJ5ID0gMSkKIyBTdGFydGluZyBwb2ludCAtIG5vIHNhbXBsZXMKcmVzID0gbW9kLmVyci5uKGRhdGEuZGlzdCA9IGRhdGEucGFyZXQsIGRhdGEuZiA9IGRhdGEucGFyZXQsIGRhdGEucmFkID0gZGF0YS5wYXJldCwgCiAgICAgICAgICAgICAgICB0ZXN0LnZlY3RvciA9IHRlc3QudmVjKQpyZXMkbiA9IDAKcmVzJHNhbXAgPSAnUmFuZG9tJwpyZXMkdW5jLm1pbiA9IHJlcyRlcnI7IHJlcyR1bmMubWF4ID0gcmVzJGVycgpyZXMkZXJyLm4gPSAxCnJlcyR1bmMubWluLm4gPSAxOyByZXMkdW5jLm1heC5uID0gMQplcnIwID0gcmVzCnJlcyRzYW1wID0gJ0FkYXB0aXZlJwoKIyBSdW4gaW4gcGFyYWxsZWwKbi5jb3JlcyA8LSBwYXJhbGxlbDo6ZGV0ZWN0Q29yZXMoKSAtIDEKbXkuY2x1c3RlciA8LSBwYXJhbGxlbDo6bWFrZUNsdXN0ZXIoCiAgbi5jb3JlcywgCiAgdHlwZSA9ICJQU09DSyIKICApCmRvUGFyYWxsZWw6OnJlZ2lzdGVyRG9QYXJhbGxlbChjbCA9IG15LmNsdXN0ZXIpCiMgZm9yZWFjaDo6Z2V0RG9QYXJSZWdpc3RlcmVkKCkKCmVyci5uID0gZm9yZWFjaChpID0gbnNhbXAsIC5jb21iaW5lID0gJ3JiaW5kJykgJWRvcGFyJSB7CiAgIyBSYW5kb20gc2FtcGxlcwogIG5zZXQgPSBpIC0gbnJvdyhkYXRhLnBhcmV0KQogIHJlcy5yYW5kbyA9IGRhdGEuZnJhbWUoKQogIGZvcihqIGluIDE6MzApeyAKICAgICMgQ29sbGVjdCBtdWx0aXBsZSByYW5kb20gZGF0YXNldHMgdG8gZ2V0IGEgc2Vuc2Ugb2YgdGhlIHZhcmlhbmNlCiAgICByYW5kbyA9IGRhdGEucmFuZG9bYygxOm5yb3coZGF0YS5wYXJldCksIAogICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlKHggPSAobnJvdyhkYXRhLnBhcmV0KSsxKTpucm93KGRhdGEucmFuZG8pLCBzaXplID0gbnNldCkpLF0KICAgIHJlcyA9IG1vZC5lcnIubihkYXRhLmRpc3QgPSByYW5kbywgZGF0YS5mID0gcmFuZG8sIGRhdGEucmFkID0gcmFuZG8sIAogICAgICAgICAgICAgICAgICAgIHRlc3QudmVjdG9yID0gdGVzdC52ZWMpCiAgICAjIE5vcm1hbGl6ZWQgdG8gdGhlIHN0YXJ0aW5nIHN0YXRlCiAgICByZXMkZXJyLm4gPSByZXMkZXJyL2VycjAkZXJyCiAgICByZXMucmFuZG8gPSByYmluZChyZXMucmFuZG8sIHJlcykKICB9CiAgZXJyID0gZGF0YS5mcmFtZSgpCiAgIyBVbmNlcnRhaW50eSBjb3JyZWN0aW9uIGJhc2VkIG9uIHRoZSB2YXJpYW5jZSAtIGluIHRoaXMgY2FzZSB3YW50IHRoZSA5NSUgQ0kKICBmb3IoY2xzIGluIHVuaXF1ZShyZXMucmFuZG8kY2xhc3MpKXsKICAgIGZvcih0cCBpbiB1bmlxdWUocmVzLnJhbmRvJHR5cCkpewogICAgICBzdWIgPSBkcGx5cjo6ZmlsdGVyKHJlcy5yYW5kbywgY2xhc3MgPT0gY2xzLCB0eXAgPT0gdHApCiAgICAgIGVyciA9IHJiaW5kKGVyciwgZGF0YS5mcmFtZSgKICAgICAgICAgIGVyciA9IG1lZGlhbihzdWIkZXJyKSwgIGVyci5uID0gbWVkaWFuKHN1YiRlcnIubiksCiAgICAgICAgICB1bmMubWluID0gcXVhbnRpbGUoeCA9IHN1YiRlcnIsIHByb2JzID0gMC4wMjUpLCAKICAgICAgICAgIHVuYy5tYXggPSBxdWFudGlsZSh4ID0gc3ViJGVyciwgcHJvYnMgPSAwLjk3NSksIAogICAgICAgICAgdW5jLm1pbi5uID0gcXVhbnRpbGUoeCA9IHN1YiRlcnIubiwgcHJvYnMgPSAwLjAyNSksIAogICAgICAgICAgdW5jLm1heC5uID0gcXVhbnRpbGUoeCA9IHN1YiRlcnIubiwgcHJvYnMgPSAwLjk3NSksIAogICAgICAgICAgY2xhc3MgPSBjbHMsIHR5cCA9IHRwLAogICAgICAgICAgbiA9IGkgLSBucm93KGRhdGEucGFyZXQpLAogICAgICAgICAgc2FtcCA9ICdSYW5kb20nKSkKICAgIH0KICB9CgogICMgQWRhcHRpdmUgc2FtcGxlcwogIGRlbHRhID0gZGF0YS5kZWx0YVsxOmksXQogIGN1dG9mID0gZGF0YS5jdXRvZlsxOmksXQogIHJhZGFuID0gZGF0YS5yYWRhblsxOmksXQogIHJlcyA9IG1vZC5lcnIubihkYXRhLmRpc3QgPSBkZWx0YSwgZGF0YS5mID0gY3V0b2YsIGRhdGEucmFkID0gcmFkYW4sIAogICAgICAgICAgICAgICAgICB0ZXN0LnZlY3RvciA9IHRlc3QudmVjKQogIHJlcyRuID0gaSAtIG5yb3coZGF0YS5wYXJldCkKICByZXMkc2FtcCA9ICdBZGFwdGl2ZScKICByZXMkZXJyLm4gPSByZXMkZXJyL2VycjAkZXJyCiAgIyBTZXQgdGhlICJ1bmNlcnRhaW50eSIgdG8gemVybyAtIHRoZSBNb250ZSBDYXJsbyBlcnJvciBpcyByZWxhdGl2ZWx5IHNtYWxsLAogICMgYW5kIGRvZXNuJ3QgcXVpdGUgcmVwcmVzZW50IHRoZSBzYW1lIGVycm9yIGFzIGFib3ZlCiAgcmVzJHVuYy5taW4gPSByZXMkZXJyCiAgcmVzJHVuYy5tYXggPSByZXMkZXJyCiAgcmVzJHVuYy5taW4ubiA9IHJlcyRlcnIubgogIHJlcyR1bmMubWF4Lm4gPSByZXMkZXJyLm4KICAKICAjIHJlcyR1bmMubWluLm4gPSByZXMkdW5jLm1pbi9lcnIwJGVycgogICMgcmVzJHVuYy5tYXgubiA9IHJlcyR1bmMubWF4L2VycjAkZXJyCiAgIyByZXMkdW5jLm4gPSBzcXJ0KHJlcyRlcnIubl4yICogKChyZXMkdW5jL3JlcyRlcnIpXjIgKyAoZXJyMCR1bmMvZXJyMCRlcnIpXjIpKQogIHJiaW5kKGVyciwgcmVzKQp9CgplcnIubiA9IHJiaW5kKGVycjAsIHJlcywgZXJyLm4pCmVyci5uCmdncGxvdChlcnIubikgKwogIGdlb21fZXJyb3JiYXIobWFwcGluZyA9IGFlcyh4ID0gbiwgeW1pbiA9IHVuYy5taW4sIHltYXggPSB1bmMubWF4LCB5ID0gZXJyLCAKICAgICAgICAgICAgICAgICAgICBjb2xvciA9IHNhbXApLCB3aWR0aCA9IDAuNSkgKwogIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4ID0gbiwgeSA9IGVyciwgY29sb3IgPSBzYW1wKSkgKwogIGZhY2V0X3dyYXAoZmFjdG9yKHR5cCwgbGV2ZWxzID0gYygnVG90YWwnLCAnRmFsc2UgUG9zaXRpdmUnLCAKICAgICAgICAgICAgICAnRmFsc2UgTmVnYXRpdmUnKSl+Y2xhc3MsIHNjYWxlcyA9ICdmcmVlX3knKSArCiAgbGFicyh4ID0gJ0FkZGl0aW9uYWwgU2FtcGxlcycsIHkgPSAnRXJyb3IgUmF0ZScpCmdncGxvdChlcnIubikgKwogIGdlb21fZXJyb3JiYXIobWFwcGluZyA9IGFlcyh4ID0gbiwgeW1pbiA9IHVuYy5taW4ubiwgeW1heCA9IHVuYy5tYXgubiwgeSA9IGVyciwgCiAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBzYW1wKSwgd2lkdGggPSAwLjUpICsKICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeCA9IG4sIHkgPSBlcnIubiwgY29sb3IgPSBzYW1wKSkgKwogIGZhY2V0X2dyaWQoZmFjdG9yKHR5cCwgbGV2ZWxzID0gYygnVG90YWwnLCAnRmFsc2UgUG9zaXRpdmUnLCAKICAgICAgICAgICAgICAnRmFsc2UgTmVnYXRpdmUnKSl+Y2xhc3MsIHNjYWxlcyA9ICdmcmVlX3knKSArCiAgbGFicyh4ID0gJ0FkZGl0aW9uYWwgU2FtcGxlcycsIHkgPSAnRXJyb3IgUmF0ZScpCgp3cml0ZS5jc3YoZXJyLm4sICdFcnJvclJhdGVzLU5zYW1wLmNzdicsIHJvdy5uYW1lcyA9IEYpCgpwYXJhbGxlbDo6c3RvcENsdXN0ZXIoY2wgPSBteS5jbHVzdGVyKQpybShlcnIwLCByZXMsIG5zYW1wLCB0ZXN0LnZlYykKCmBgYAoKCiMgRXhpc3RpbmcgTWV0aG9kOiBTdXBwb3J0IFZlY3RvciBNYWNoaW5lcwoKU1ZNIG1ldGhvZHMgcmVxdWlyZSBleGlzdGluZyBsYWJlbHMgZm9yIHRoZSBwb2ludHM7IGluIHRoaXMgY2FzZSwgdGhlIGxhYmVscyBhcmUgd2hldGhlciBvciBub3QgdGhlIHBvaW50IG1lZXRzIHRoZSBzYW1lIHRocmVlIHNlbGVjdGlvbiBjcml0ZXJpYSBhcyB0ZXN0ZWQgd2l0aCB0aGUgbmV3IEdQIG1ldGhvZC4KCldoaWxlIGFsbCA0IGtlcm5lbCB0eXBlcyBhdmFpbGFibGUgaW4gdGhlIGUxMDcxIHBhY2thZ2UgYXJlIGNhbGN1bGF0ZWQsIHRoZSBsaW5lYXIgYW5kIHNpZ21vaWRhbCBrZXJuZWxzIGNvbnNpc3RlbnRseSBnYXZlIHBvb3IgcmVzdWx0cywgYW5kIGFyZSBleGNsdWRlZCBmcm9tIHRoZSBhY2N1cmFjeSBjYWxjdWxhdGlvbi4KCmBgYHtyIFNWTTogRnVuY3Rpb25zfQojIENyZWF0ZSBhIGZ1bmN0aW9uIHRvIG91dHB1dCB0aGUgcGxvdCBvZiBhbGwgNCBtb2RlbHMgY29tcGFyZWQgdG8gdGhlIHJlYWwgcmVzdWx0ClNWTS5pbnB1dCA9IGZ1bmN0aW9uKGZpbmUuaW5wdXQsIG1vZC5yYWQsIG1vZC5saW4sIG1vZC5wb2wsIG1vZC5zaWcsIHRpdCl7CiAgIyBGaW5lIGlucHV0IGhhcyB0aGUgdmFyaWFibGVzIHgxLCB4MiwgYW5kIGNhdAogIHJlcyA9IHByZWRpY3Qob2JqZWN0ID0gbW9kLmxpbiwgbmV3ZGF0YSA9IGZpbmUuaW5wdXRbLCBjKCd4MScsICd4MicpXSkKICBmaW5lLmlucHV0JHJlcyA9IHJlcwogIGcubGluID0gZ2dwbG90KGZpbmUuaW5wdXQpICsKICAgIGdlb21fcG9pbnQoZGF0YSA9IGZpbmUuaW5wdXQsIG1hcHBpbmcgPSBhZXMoeCA9IHgxLCB5ID0geDIsIGNvbG9yID0gcmVzKSkgKwogICAgZ2VvbV9jb250b3VyKGRhdGEgPSBmaW5lLmlucHV0LCBtYXBwaW5nID0gYWVzKHggPSB4MSwgeSA9IHgyLCB6ID0gY2F0KSwgYnJlYWtzID0gYygwLjUpKSArCiAgICBsYWJzKHN1YnRpdGxlID0gJ0xpbmVhcicsIHggPSBleHByZXNzaW9uKCd4J1sxXSksIHkgPSBleHByZXNzaW9uKCd4J1syXSkpICsgCiAgICBndWlkZXMoY29sb3IgPSBGQUxTRSkKICByZXMgPSBwcmVkaWN0KG9iamVjdCA9IG1vZC5yYWQsIG5ld2RhdGEgPSBmaW5lLmlucHV0WyxjKCd4MScsICd4MicpXSkKICBmaW5lLmlucHV0JHJlcyA9IHJlcwogIGcucmFkID0gZ2dwbG90KCkgKwogICAgZ2VvbV9wb2ludChkYXRhID0gZmluZS5pbnB1dCwgbWFwcGluZyA9IGFlcyh4ID0geDEsIHkgPSB4MiwgY29sb3IgPSByZXMpKSArCiAgICBnZW9tX2NvbnRvdXIoZGF0YSA9IGZpbmUuaW5wdXQsIG1hcHBpbmcgPSBhZXMoeCA9IHgxLCB5ID0geDIsIHogPSBjYXQpLCBicmVha3MgPSBjKDAuNSkpICsKICAgIGxhYnMoc3VidGl0bGUgPSAnUmFkaWFsJywgeCA9IGV4cHJlc3Npb24oJ3gnWzFdKSwgeSA9IGV4cHJlc3Npb24oJ3gnWzJdKSwgdGl0bGUgPSB0aXQpICsgZ3VpZGVzKGNvbG9yID0gRkFMU0UpCiAgcmVzID0gcHJlZGljdChvYmplY3QgPSBtb2QucG9sLCBuZXdkYXRhID0gZmluZS5pbnB1dFssYygneDEnLCAneDInKV0pCiAgZmluZS5pbnB1dCRyZXMgPSByZXMKICBnLnBvbCA9IGdncGxvdCgpICsKICAgIGdlb21fcG9pbnQoZGF0YSA9IGZpbmUuaW5wdXQsIG1hcHBpbmcgPSBhZXMoeCA9IHgxLCB5ID0geDIsIGNvbG9yID0gcmVzKSkgKwogICAgZ2VvbV9jb250b3VyKGRhdGEgPSBmaW5lLmlucHV0LCBtYXBwaW5nID0gYWVzKHggPSB4MSwgeSA9IHgyLCB6ID0gY2F0KSwgYnJlYWtzID0gYygwLjUpKSArCiAgICBsYWJzKHN1YnRpdGxlID0gJ1BvbHlub21pYWwnLCB4ID0gZXhwcmVzc2lvbigneCdbMV0pLCB5ID0gZXhwcmVzc2lvbigneCdbMl0pKSArIGd1aWRlcyhjb2xvciA9IEZBTFNFKQogIHJlcyA9IHByZWRpY3Qob2JqZWN0ID0gbW9kLnNpZywgbmV3ZGF0YSA9IGZpbmUuaW5wdXRbLGMoJ3gxJywgJ3gyJyldKQogIGZpbmUuaW5wdXQkcmVzID0gcmVzCiAgZy5zaWcgPSBnZ3Bsb3QoKSArCiAgICBnZW9tX3BvaW50KGRhdGEgPSBmaW5lLmlucHV0LCBtYXBwaW5nID0gYWVzKHggPSB4MSwgeSA9IHgyLCBjb2xvciA9IHJlcykpICsKICAgIGdlb21fY29udG91cihkYXRhID0gZmluZS5pbnB1dCwgbWFwcGluZyA9IGFlcyh4ID0geDEsIHkgPSB4MiwgeiA9IGNhdCksIGJyZWFrcyA9IGMoMC41KSkgKwogICAgbGFicyhzdWJ0aXRsZSA9ICdTaWdtb2lkJywgeCA9IGV4cHJlc3Npb24oJ3gnWzFdKSwgeSA9IGV4cHJlc3Npb24oJ3gnWzJdKSkgKyBndWlkZXMoY29sb3IgPSBGQUxTRSkKICByZXR1cm4oKGcucmFkICsgZy5saW4pIC8gKGcucG9sICsgZy5zaWcpKQp9CgpTVk0ubW9kLmFsbCA9IGZ1bmN0aW9uKGlucHV0LmRhdGEpewogICMgR2l2ZW4gaW5wdXQgZGF0YSwgZmluZCBhbGwgNCBkZWZhdWx0IG1vZGVscyBhbmQgcmV0dXJuIGFzIGEgbGlzdAogIGZpdC5yYWQgPSBlMTA3MTo6c3ZtKGFzLmZhY3RvcihjYXQpIH4geDEqeDIsIGRhdGEgPSBpbnB1dC5kYXRhWyxjKCd4MScsICd4MicsICdjYXQnKV0sIAogICAgICAgICAgICBzY2FsZSA9IEZBTFNFLCBrZXJuZWwgPSAicmFkaWFsIiwgY29zdCA9IDUpCiAgZml0LmxpbiA9IGUxMDcxOjpzdm0oYXMuZmFjdG9yKGNhdCkgfiB4MSp4MiwgZGF0YSA9IGlucHV0LmRhdGFbLGMoJ3gxJywgJ3gyJywgJ2NhdCcpXSwgCiAgICAgICAgICAgIHNjYWxlID0gRkFMU0UsIGtlcm5lbCA9ICJsaW5lYXIiLCBjb3N0ID0gNSkKICAjIFBvbHlub21pYWw6IGluY3JlYXNlIHRoZSBrZXJuZWwgZGVncmVlIGR1ZSB0byBjb21wbGV4aXR5IG9mIHRoZSBib3VuZGFyeTsgbWF4aW11bSB0ZXN0ZWQgdGhhdCBzdGlsbCBhY2hpZXZlZCBjb252ZXJnZW5jZQogIGZpdC5wb2wgPSBlMTA3MTo6c3ZtKGFzLmZhY3RvcihjYXQpIH4geDEqeDIsIGRhdGEgPSBpbnB1dC5kYXRhWyxjKCd4MScsICd4MicsICdjYXQnKV0sIAogICAgICAgICAgICBzY2FsZSA9IEZBTFNFLCBrZXJuZWwgPSAicG9seW5vbWlhbCIsIGNvc3QgPSA1LCBkZWdyZWUgPSAzLjUpCiAgIyBJbmNyZWFzZSB0aGUgY29lZmZpY2llbnQKICBmaXQuc2lnID0gZTEwNzE6OnN2bShhcy5mYWN0b3IoY2F0KSB+IHgxKngyLCBkYXRhID0gaW5wdXQuZGF0YVssYygneDEnLCAneDInLCAnY2F0JyldLCAKICAgICAgICAgICAgc2NhbGUgPSBGQUxTRSwga2VybmVsID0gInNpZ21vaWQiLCBjb3N0ID0gNSkKICByZXR1cm4obGlzdChyYWQgPSBmaXQucmFkLCBsaW4gPSBmaXQubGluLCBwb2wgPSBmaXQucG9sLCBzaWcgPSBmaXQuc2lnKSkKfQpTVk0uZXJyID0gZnVuY3Rpb24oZmluZS5pbnB1dCwgbW9kLmxpc3QpewogICMgRmluZSBncmlkIGhhcyB0aGUgcmVhbCByZXN1bHQ7IG1vZC5saXN0IGlzIHRoZSA0IGRpZmZlcmVudCBTVk0ga2VybmVscwogIGZpbmUuaW5wdXQkcmFkID0gcHJlZGljdChvYmplY3QgPSBtb2QubGlzdCRyYWQsIG5ld2RhdGEgPSBmaW5lLmlucHV0WyxjKCd4MScsICd4MicpXSkKICBmaW5lLmlucHV0JGxpbiA9IHByZWRpY3Qob2JqZWN0ID0gbW9kLmxpc3QkbGluLCBuZXdkYXRhID0gZmluZS5pbnB1dFssYygneDEnLCAneDInKV0pCiAgZmluZS5pbnB1dCRwb2wgPSBwcmVkaWN0KG9iamVjdCA9IG1vZC5saXN0JHBvbCwgbmV3ZGF0YSA9IGZpbmUuaW5wdXRbLGMoJ3gxJywgJ3gyJyldKQogICMgZmluZS5pbnB1dCRzaWcgPSBwcmVkaWN0KG9iamVjdCA9IG1vZC5saXN0JHNpZywgbmV3ZGF0YSA9IGZpbmUuaW5wdXRbLGMoJ3gxJywgJ3gyJyldKQogIAogICMgRXJyb3IgcmF0ZQogIHJhZCA9IG5yb3coZHBseXI6OmZpbHRlcihmaW5lLmlucHV0LCBjYXQgPT0gMCwgcmFkID09IDEpKSArIAogICAgbnJvdyhkcGx5cjo6ZmlsdGVyKGZpbmUuaW5wdXQsIGNhdCA9PSAxLCByYWQgPT0gMCkpCiAgbGluID0gbnJvdyhkcGx5cjo6ZmlsdGVyKGZpbmUuaW5wdXQsIGNhdCA9PSAwLCBsaW4gPT0gMSkpICsgCiAgICBucm93KGRwbHlyOjpmaWx0ZXIoZmluZS5pbnB1dCwgY2F0ID09IDEsIGxpbiA9PSAwKSkKICBwb2wgPSBucm93KGRwbHlyOjpmaWx0ZXIoZmluZS5pbnB1dCwgY2F0ID09IDAsIHBvbCA9PSAxKSkgKyAKICAgIG5yb3coZHBseXI6OmZpbHRlcihmaW5lLmlucHV0LCBjYXQgPT0gMSwgcG9sID09IDApKQogICMgc2lnID0gbnJvdyhmaWx0ZXIoZmluZS5pbnB1dCwgY2F0ID09IDAsIHNpZyA9PSAxKSkgKyBucm93KGZpbHRlcihmaW5lLmlucHV0LCBjYXQgPT0gMSwgc2lnID09IDApKQogICMgcmF0ZSA9IGMocmFkLCBsaW4sIHBvbCwgc2lnKS9ucm93KGZpbmUuaW5wdXQpCiAgcmF0ZSA9IGMocmFkLCBsaW4sIHBvbCkvbnJvdyhmaW5lLmlucHV0KQogICMgVW5jZXJ0YWludHkKICBlcnIgPSBzcXJ0KHJhdGUqKDEtcmF0ZSkvbnJvdyhmaW5lLmlucHV0KSkKICAKICAjIFR5cGUgSSBlcnJvciA9IFBbc2hvdWxkIHJlamVjdCB8IGFjY2VwdGVkXSA9IFBbYm90aF0gUFtzaG91bGQgcmVqZWN0XSAvIFBbYWNjZXB0ZWRdCiAgdHlwMSA9CiAgICBjKG5yb3coZHBseXI6OmZpbHRlcihmaW5lLmlucHV0LCBjYXQgPT0gMCwgcmFkID09IDEpKSoKICAgICAgICBucm93KGRwbHlyOjpmaWx0ZXIoZmluZS5pbnB1dCwgY2F0ID09IDApKS9ucm93KGZpbmUuaW5wdXQpLwogICAgICAgIG5yb3coZHBseXI6OmZpbHRlcihmaW5lLmlucHV0LCByYWQgPT0gMSkpLAogICAgICBucm93KGRwbHlyOjpmaWx0ZXIoZmluZS5pbnB1dCwgY2F0ID09IDAsIGxpbiA9PSAxKSkqCiAgICAgICAgbnJvdyhkcGx5cjo6ZmlsdGVyKGZpbmUuaW5wdXQsIGNhdCA9PSAwKSkvbnJvdyhmaW5lLmlucHV0KS8KICAgICAgICBucm93KGRwbHlyOjpmaWx0ZXIoZmluZS5pbnB1dCwgbGluID09IDEpKSwKICAgICAgbnJvdyhkcGx5cjo6ZmlsdGVyKGZpbmUuaW5wdXQsIGNhdCA9PSAwLCBwb2wgPT0gMSkpKgogICAgICAgIG5yb3coZHBseXI6OmZpbHRlcihmaW5lLmlucHV0LCBjYXQgPT0gMCkpL25yb3coZmluZS5pbnB1dCkvCiAgICAgICAgbnJvdyhkcGx5cjo6ZmlsdGVyKGZpbmUuaW5wdXQsIHBvbCA9PSAxKSkpIywKICAgICAgIyBucm93KGZpbHRlcihmaW5lLmlucHV0LCBjYXQgPT0gMCwgc2lnID09IDEpKSpucm93KGZpbHRlcihmaW5lLmlucHV0LCBjYXQgPT0gMCkpL25yb3coZmluZS5pbnB1dCkvCiAgICAgICAgIyBucm93KGZpbHRlcihmaW5lLmlucHV0LCBzaWcgPT0gMSkpICkKICB0eXAxLmVyciA9IHNxcnQodHlwMSooMS10eXAxKSAvIG5yb3coZmluZS5pbnB1dCkpICAKICAKICAjIFR5cGUgSUkgZXJyb3IgPSBQW3Nob3VsZCBhY2NlcHQgfCByZWplY3RlZF0gPSBQW2JvdGhdIFBbc2hvdWxkIGFjY2VwdF0gLyBQW3JlamVjdGVkXQogIHR5cDIgPQogICAgYyhucm93KGRwbHlyOjpmaWx0ZXIoZmluZS5pbnB1dCwgY2F0ID09IDEsIHJhZCA9PSAwKSkqCiAgICAgICAgbnJvdyhkcGx5cjo6ZmlsdGVyKGZpbmUuaW5wdXQsIGNhdCA9PSAxKSkvbnJvdyhmaW5lLmlucHV0KS8KICAgICAgICBucm93KGRwbHlyOjpmaWx0ZXIoZmluZS5pbnB1dCwgcmFkID09IDApKSwKICAgICAgbnJvdyhkcGx5cjo6ZmlsdGVyKGZpbmUuaW5wdXQsIGNhdCA9PSAxLCBsaW4gPT0gMCkpKgogICAgICAgIG5yb3coZHBseXI6OmZpbHRlcihmaW5lLmlucHV0LCBjYXQgPT0gMSkpL25yb3coZmluZS5pbnB1dCkvCiAgICAgICAgbnJvdyhkcGx5cjo6ZmlsdGVyKGZpbmUuaW5wdXQsIGxpbiA9PSAwKSksCiAgICAgIG5yb3coZHBseXI6OmZpbHRlcihmaW5lLmlucHV0LCBjYXQgPT0gMSwgcG9sID09IDApKSoKICAgICAgICBucm93KGRwbHlyOjpmaWx0ZXIoZmluZS5pbnB1dCwgY2F0ID09IDEpKS9ucm93KGZpbmUuaW5wdXQpLwogICAgICAgIG5yb3coZHBseXI6OmZpbHRlcihmaW5lLmlucHV0LCBwb2wgPT0gMCkpKSMsCiAgICAgICMgbnJvdyhmaWx0ZXIoZmluZS5pbnB1dCwgY2F0ID09IDEsIHNpZyA9PSAwKSkqbnJvdyhmaWx0ZXIoZmluZS5pbnB1dCwgY2F0ID09IDEpKS9ucm93KGZpbmUuaW5wdXQpLwogICAgICAgICMgbnJvdyhmaWx0ZXIoZmluZS5pbnB1dCwgc2lnID09IDApKSApCiAgdHlwMi5lcnIgPSBzcXJ0KHR5cDIqKDEtdHlwMikgLyBucm93KGZpbmUuaW5wdXQpKQogICMgcmV0dXJuKGRhdGEuZnJhbWUobWV0aG9kID0gYygnU1ZNLXJhZCcsICdTVk0tbGluJywgJ1NWTS1wb2wnLCAnU1ZNLXNpZycpLCByYXRlLCBlcnIsIHR5cDEsIHR5cDEuZXJyLCB0eXAyLCB0eXAyLmVycikpCiAgcmV0dXJuKGRhdGEuZnJhbWUobWV0aG9kID0gYygnU1ZNLXJhZCcsICdTVk0tbGluJywgJ1NWTS1wb2wnKSwgcmF0ZSwgZXJyLCB0eXAxLCB0eXAxLmVyciwgdHlwMiwgdHlwMi5lcnIpKQp9CgojIEZvciB0aGUgcmFkaWFsIGFuZCBwb2x5bm9taWFsIGtlcm5lbHMsIGNhbGN1bGF0ZSB0aGUgbWFyZ2luYWxzIGFuZCBTaGFwbGV5IHZhbHVlcyBmb3IgY29tcGFyaXNvbgptYXJnaW5hbCA9IGZ1bmN0aW9uKG1vZCl7CiAgeC5ybmcgPSBzZXEoZnJvbSA9IDAsIHRvID0gNSwgbGVuZ3RoLm91dCA9IDUwKQogIHN2bS5tYXJnaW4gPSBkYXRhLmZyYW1lKCkKICBuc2FtcCA9IDIwMDAKICBmb3IoeCBpbiB4LnJuZyl7CiAgICAjIHgxIG1hcmdpbmFsCiAgICB0ZW1wLmZyYW1lID0gZGF0YS5mcmFtZSh4MSA9IHgsIHgyID0gcnVuaWYobiA9IG5zYW1wLCBtaW4gPSAwLCBtYXggPSA1KSkKICAgIHJlcyA9IGFzLm51bWVyaWMocHJlZGljdChvYmplY3QgPSBtb2QkcG9sLCBuZXdkYXRhID0gdGVtcC5mcmFtZSkpLTEKICAgIHN2bS5tYXJnaW4gPSByYmluZChzdm0ubWFyZ2luLCBkYXRhLmZyYW1lKHggPSB4LCB2YXIgPSAneDEnLCBwcm9iID0gc3VtKHJlcykvbGVuZ3RoKHJlcyksIG1ldGhvZCA9ICdTVk0tcG9sJykpCiAgICByZXMgPSBhcy5udW1lcmljKHByZWRpY3Qob2JqZWN0ID0gbW9kJHJhZCwgbmV3ZGF0YSA9IHRlbXAuZnJhbWUpKS0xCiAgICBzdm0ubWFyZ2luID0gcmJpbmQoc3ZtLm1hcmdpbiwgZGF0YS5mcmFtZSh4ID0geCwgdmFyID0gJ3gxJywgcHJvYiA9IHN1bShyZXMpL2xlbmd0aChyZXMpLCBtZXRob2QgPSAnU1ZNLXJhZCcpKQogICAgIyB4MSBtYXJnaW5hbAogICAgdGVtcC5mcmFtZSA9IGRhdGEuZnJhbWUoeDIgPSB4LCB4MSA9IHJ1bmlmKG4gPSBuc2FtcCwgbWluID0gMCwgbWF4ID0gNSkpCiAgICByZXMgPSBhcy5udW1lcmljKHByZWRpY3Qob2JqZWN0ID0gbW9kJHBvbCwgbmV3ZGF0YSA9IHRlbXAuZnJhbWUpKS0xCiAgICBzdm0ubWFyZ2luID0gcmJpbmQoc3ZtLm1hcmdpbiwgZGF0YS5mcmFtZSh4ID0geCwgdmFyID0gJ3gyJywgcHJvYiA9IHN1bShyZXMpL2xlbmd0aChyZXMpLCBtZXRob2QgPSAnU1ZNLXBvbCcpKQogICAgcmVzID0gYXMubnVtZXJpYyhwcmVkaWN0KG9iamVjdCA9IG1vZCRyYWQsIG5ld2RhdGEgPSB0ZW1wLmZyYW1lKSktMQogICAgc3ZtLm1hcmdpbiA9IHJiaW5kKHN2bS5tYXJnaW4sIGRhdGEuZnJhbWUoeCA9IHgsIHZhciA9ICd4MicsIHByb2IgPSBzdW0ocmVzKS9sZW5ndGgocmVzKSwgbWV0aG9kID0gJ1NWTS1yYWQnKSkKICB9CiAgCiAgc3ZtLm1hcmdpbiRwc2QgPSBzcXJ0KHN2bS5tYXJnaW4kcHJvYiooMSAtIHN2bS5tYXJnaW4kcHJvYikvbnNhbXApCiAgcmV0dXJuKHN2bS5tYXJnaW4pCn0KClNWTS5zaGFwID0gZnVuY3Rpb24obW9kKXsKICAjIFN0cnVtYmVsaiBldCBhbC4gKDIwMTQpIE1vbnRlIENhcmxvIGVzdGltYXRlLgogICMgU2luY2UgdGhpcyBpcyBhIGNsYXNzaWZpY2F0aW9uIHNldHRpbmcsIG9ubHkgYSBkaWZmZXJlbmNlIG9mIDAgb3IgMSBpcyBwb3NzaWJsZSAKICAjIGkuZS4gYSBkaWZmZXJlbmNlIG9mIC0xIGlzIHRoZSBzYW1lIGFzIGEgZGlmZmVyZW5jZSBvZiAxLCBhcyBpdCBpcyBzaW1wbHkgYSBtaXNjbGFzc2lmaWNhdGlvbgogIG5zYW1wID0gMTUwMCo1MCAjIFNhbWUgbnVtYmVyIG9mIHNhbXBsZXMgYXMgb3RoZXIgbWV0aG9kcyBmb3IgY29uc2lzdGVuY3kKICB4MCA9IGRhdGEuZnJhbWUoeDEgPSBydW5pZihuID0gbnNhbXAsIG1pbiA9IDAsIG1heCA9IDUpLAogICAgICAgICAgICAgICAgICB4MiA9IHJ1bmlmKG4gPSBuc2FtcCwgbWluID0gMCwgbWF4ID0gNSkpCiAgejEgPSBkYXRhLmZyYW1lKHgxID0geDAkeDEsCiAgICAgICAgICAgICAgICAgIHgyID0gcnVuaWYobiA9IG5zYW1wLCBtaW4gPSAwLCBtYXggPSA1KSkKICB6MiA9IGRhdGEuZnJhbWUoeDEgPSBydW5pZihuID0gbnNhbXAsIG1pbiA9IDAsIG1heCA9IDUpLAogICAgICAgICAgICAgICAgICB4MiA9IHgwJHgyKQogICMgQXBwbHkgZnVuY3Rpb25zIHRvIGFsbAogIHJlcy5wb2wgPSBhcy5udW1lcmljKHByZWRpY3Qob2JqZWN0ID0gbW9kJHBvbCwgbmV3ZGF0YSA9IHgwKSkKICByZXMucmFkID0gYXMubnVtZXJpYyhwcmVkaWN0KG9iamVjdCA9IG1vZCRyYWQsIG5ld2RhdGEgPSB4MCkpCiAgeDAkcG9sID0gcmVzLnBvbDsgeDAkcmFkID0gcmVzLnJhZAogIHJlcy5wb2wgPSBhcy5udW1lcmljKHByZWRpY3Qob2JqZWN0ID0gbW9kJHBvbCwgbmV3ZGF0YSA9IHoxKSkKICByZXMucmFkID0gYXMubnVtZXJpYyhwcmVkaWN0KG9iamVjdCA9IG1vZCRyYWQsIG5ld2RhdGEgPSB6MSkpCiAgejEkcG9sID0gcmVzLnBvbDsgejEkcmFkID0gcmVzLnJhZAogIHJlcy5wb2wgPSBhcy5udW1lcmljKHByZWRpY3Qob2JqZWN0ID0gbW9kJHBvbCwgbmV3ZGF0YSA9IHoyKSkKICByZXMucmFkID0gYXMubnVtZXJpYyhwcmVkaWN0KG9iamVjdCA9IG1vZCRyYWQsIG5ld2RhdGEgPSB6MikpCiAgejIkcG9sID0gcmVzLnBvbDsgejIkcmFkID0gcmVzLnJhZAogICMgQ2FsY3VsYXRlIG1lYW4gYW5kIHN0YW5kYXJkIGVycm9yIG9mIHRoZSBkaWZmZXJlbmNlcyBmb3IgU2hhcGxleSB2YWx1ZXMKICBzaGFwID0gYyhtZWFuKCh4MCRwb2wgLSB6MSRwb2wpKSwgbWVhbigoeDAkcG9sIC0gejIkcG9sKSksCiAgICAgICAgICAgbWVhbigoeDAkcmFkIC0gejEkcmFkKSksIG1lYW4oKHgwJHJhZCAtIHoyJHJhZCkpKQogIHIuc2hhcCA9IGMoc2hhcFsxOjJdL21heChzaGFwWzE6Ml0pLCBzaGFwWzM6NF0vbWF4KHNoYXBbMzo0XSkpCiAgIyBGb3Igc3RhbmRhcmQgZXJyb3IsIGNhbGN1bGF0ZSBzdGFuZGFyZCBlcnJvciBvZiB0aGUgbWVhbiB3aXRoIG4gPSAxNTAwCiAgIyB0byBiZSBjb25zaXN0ZW50IHdpdGggdGhlIG90aGVyIHN0YW5kYXJkIGVycm9ycwogIHBvbC54MW0gPSBhcHBseShtYXRyaXgoeDAkcG9sIC0gejEkcG9sLCBucm93ID0gMTUwMCksIDIsIG1lYW4pCiAgcmFkLngxbSA9IGFwcGx5KG1hdHJpeCh4MCRyYWQgLSB6MSRyYWQsIG5yb3cgPSAxNTAwKSwgMiwgbWVhbikKICBwb2wueDJtID0gYXBwbHkobWF0cml4KHgwJHBvbCAtIHoyJHBvbCwgbnJvdyA9IDE1MDApLCAyLCBtZWFuKQogIHJhZC54Mm0gPSBhcHBseShtYXRyaXgoeDAkcmFkIC0gejIkcmFkLCBucm93ID0gMTUwMCksIDIsIG1lYW4pCiAgCiAgc2hhcC5lcnIgPSBjKHNkKHBvbC54MW0pLCBzZChwb2wueDJtKSwKICAgICAgICAgICAgICAgc2QocmFkLngxbSksIHNkKHJhZC54Mm0pKQogIHJldHVybihkYXRhLmZyYW1lKGltcG9ydCA9IHNoYXAsIHZhciA9IGMoJ3gxJywgJ3gyJyksIAogICAgICAgICAgICAgICAgICAgIHNkID0gc2hhcC5lcnIsIHIuaW1wb3J0ID0gci5zaGFwLCAKICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSBjKCdTaGFwbGV5LXBvbCcsICdTaGFwbGV5LXBvbCcsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1NoYXBsZXktcmFkJywgJ1NoYXBsZXktcmFkJykpKQp9CgpgYGAKCmBgYHtyIFNWTTogRGlzdGFuY2V9CmRhdGEucGFyZXQgPSByZWFkLmNzdignLi4vRXhfUXVhcnRpYy9HUGFyX2FsbF9zdGFydC5jc3YnKQpkYXRhLmRlbHRhID0gcmVhZC5jc3YoJy4uL0V4X1F1YXJ0aWMvR1Bhcl9BY2NlcHRfRGVsdGExLmNzdicpCmRhdGEucmFuZG8gPSByZWFkLmNzdihmaWxlID0gJ0dQYXJfUmFuZG9tLmNzdicpCmRhdGEucmFuZG8gPSBkYXRhLnJhbmRvWzE6bnJvdyhkYXRhLnBhcmV0KSwgXQoKIyBEZWZpbmUgYWNjZXB0YW5jZQpkYXRhLnBhcmV0JGNhdCA9IDA7IGRhdGEuZGVsdGEkY2F0ID0gMDsgZGF0YS5yYW5kbyRjYXQgPSAwOyBmaW5lLmdyaWQkY2F0ID0gMApkYXRhLnBhcmV0JGNhdFtkYXRhLnBhcmV0JGRpc3QgPD0gMV0gPSAxCmRhdGEuZGVsdGEkY2F0W2RhdGEuZGVsdGEkZGlzdCA8PSAxXSA9IDEKZGF0YS5yYW5kbyRjYXRbZGF0YS5yYW5kbyRkaXN0IDw9IDFdID0gMQpmaW5lLmdyaWQkY2F0W2ZpbmUuZ3JpZCRkaXN0IDw9IDFdID0gMQoKIyBQbG90cwptb2QucGFyZXQgPSBTVk0ubW9kLmFsbChpbnB1dC5kYXRhID0gZGF0YS5wYXJldCkKU1ZNLmlucHV0KGZpbmUuaW5wdXQgPSBmaW5lLmdyaWRbLGMoJ3gxJywgJ3gyJywgJ2NhdCcpXSwgCiAgICAgICAgICBtb2QucmFkID0gbW9kLnBhcmV0JHJhZCwgbW9kLmxpbiA9IG1vZC5wYXJldCRsaW4sIAogICAgICAgICAgbW9kLnBvbCA9IG1vZC5wYXJldCRwb2wsIG1vZC5zaWcgPSBtb2QucGFyZXQkc2lnLAogICAgICAgICAgdGl0ID0gJ1BhcmV0byBEaXN0YW5jZSwgU3RhcnRpbmcgRGF0YXNldCcpCgptb2QuYWRhcHQgPSBTVk0ubW9kLmFsbChpbnB1dC5kYXRhID0gZGF0YS5kZWx0YSkKIyBTdG9yZSB0aGlzIG1vZGVsIGZvciBpbXBvcnRhbmNlIHJhbmtpbmcgbGF0ZXIKbW9kLmFkYXB0LmRlbHRhID0gbW9kLmFkYXB0ClNWTS5pbnB1dChmaW5lLmlucHV0ID0gZmluZS5ncmlkWyxjKCd4MScsICd4MicsICdjYXQnKV0sIAogICAgICAgICAgbW9kLnJhZCA9IG1vZC5hZGFwdCRyYWQsIG1vZC5saW4gPSBtb2QuYWRhcHQkbGluLCAKICAgICAgICAgIG1vZC5wb2wgPSBtb2QuYWRhcHQkcG9sLCBtb2Quc2lnID0gbW9kLmFkYXB0JHNpZywKICAgICAgICAgIHRpdCA9ICdQYXJldG8gRGlzdGFuY2UsICsgQWRhcHRpdmUgU2FtcGxpbmcnKQoKbW9kLnJhbmRvID0gU1ZNLm1vZC5hbGwoaW5wdXQuZGF0YSA9IGRhdGEucmFuZG8pClNWTS5pbnB1dChmaW5lLmlucHV0ID0gZmluZS5ncmlkWyxjKCd4MScsICd4MicsICdjYXQnKV0sIAogICAgICAgICAgbW9kLnJhZCA9IG1vZC5yYW5kbyRyYWQsIG1vZC5saW4gPSBtb2QucmFuZG8kbGluLCAKICAgICAgICAgIG1vZC5wb2wgPSBtb2QucmFuZG8kcG9sLCBtb2Quc2lnID0gbW9kLnJhbmRvJHNpZywKICAgICAgICAgIHRpdCA9ICdQYXJldG8gRGlzdGFuY2UsICsgUmFuZG9tIFNhbXBsaW5nJykKCmVyci5wYXJldCA9IFNWTS5lcnIoZmluZS5pbnB1dCA9IGZpbmUuZ3JpZCwgbW9kLmxpc3QgPSBtb2QucGFyZXQpCmVyci5wYXJldCRzb3VyY2UgPSAnMHBhcmV0JzsgZXJyLnBhcmV0JGNyaXRlcmlhID0gJ1BhcmV0byBEaXN0YW5jZScKZXJyLmFkYXB0ID0gU1ZNLmVycihmaW5lLmlucHV0ID0gZmluZS5ncmlkLCBtb2QubGlzdCA9IG1vZC5hZGFwdCkKZXJyLmFkYXB0JHNvdXJjZSA9ICcxYWRhcHQnOyBlcnIuYWRhcHQkY3JpdGVyaWEgPSAnUGFyZXRvIERpc3RhbmNlJwplcnIucmFuZG8gPSBTVk0uZXJyKGZpbmUuaW5wdXQgPSBmaW5lLmdyaWQsIG1vZC5saXN0ID0gbW9kLnJhbmRvKQplcnIucmFuZG8kc291cmNlID0gJzJyYW5kbyc7IGVyci5yYW5kbyRjcml0ZXJpYSA9ICdQYXJldG8gRGlzdGFuY2UnCgplcnIuc3ZtLmRpc3QgPSByYmluZChlcnIucGFyZXQsIGVyci5hZGFwdCwgZXJyLnJhbmRvKQoKZy5yYXRlID0gZ2dwbG90KGVyci5zdm0uZGlzdCkgKwogIGdlb21fY29sKG1hcHBpbmcgPSBhZXMoeCA9IHNvdXJjZSwgeSA9IHJhdGUsIGZpbGwgPSBzb3VyY2UpKSArCiAgZ2VvbV9lcnJvcmJhcihtYXBwaW5nID0gYWVzKHggPSBzb3VyY2UsIHltaW4gPSByYXRlIC0gZXJyLCB5bWF4ID0gcmF0ZSArIGVyciksIHdpZHRoID0gMC41KSArCiAgZmFjZXRfd3JhcCh+bWV0aG9kLCBucm93ID0gMSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKGxhYmVscyA9IGMoJzBwYXJldCcgPSAnU3RhcnRpbmcgRGF0YXNldCcsICcxYWRhcHQnID0gJysgQWRhcHRpdmUgU2FtcGxpbmcnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJzJyYW5kbycgPSAnKyBSYW5kb20gU2FtcGxpbmcnKSwgbmFtZSA9ICcnLAogICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYygnMHBhcmV0JyA9ICdza3libHVlMicsICcxYWRhcHQnID0gJ3JlZCcsICcycmFuZG8nID0gJ2dyZWVuJykpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gZXhwYW5zaW9uKG11bHQgPSBjKDAsIDAuMDUpKSkgKyBzY2FsZV94X2Rpc2NyZXRlKGJyZWFrcyA9ICcnKSArCiAgdGhlbWVfYncoKSArIGxhYnMoeCA9ICcnLCB5ID0gJ1RvdGFsXG5FcnJvciBSYXRlJywgc3VidGl0bGUgPSAnUGFyZXRvIERpc3RhbmNlJykKCmcudHlwMSA9IGdncGxvdChlcnIuc3ZtLmRpc3QpICsKICBnZW9tX2NvbChtYXBwaW5nID0gYWVzKHggPSBzb3VyY2UsIHkgPSB0eXAxLCBmaWxsID0gc291cmNlKSkgKwogIGdlb21fZXJyb3JiYXIobWFwcGluZyA9IGFlcyh4ID0gc291cmNlLCB5bWluID0gdHlwMSAtIHR5cDEuZXJyLCB5bWF4ID0gdHlwMSArIHR5cDEuZXJyKSwgd2lkdGggPSAwLjUpICsKICBmYWNldF93cmFwKH5tZXRob2QsIG5yb3cgPSAxKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobGFiZWxzID0gYygnMHBhcmV0JyA9ICdTdGFydGluZyBEYXRhc2V0JywgJzFhZGFwdCcgPSAnKyBBZGFwdGl2ZSBTYW1wbGluZycsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnMnJhbmRvJyA9ICcrIFJhbmRvbSBTYW1wbGluZycpLCBuYW1lID0gJycsCiAgICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBjKCcwcGFyZXQnID0gJ3NreWJsdWUyJywgJzFhZGFwdCcgPSAncmVkJywgJzJyYW5kbycgPSAnZ3JlZW4nKSkgKwogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBleHBhbnNpb24obXVsdCA9IGMoMCwgMC4wNSkpKSArIHNjYWxlX3hfZGlzY3JldGUoYnJlYWtzID0gJycpICsKICB0aGVtZV9idygpICsgbGFicyh4ID0gJycsIHkgPSAnRmFsc2UgUG9zaXRpdmVcbkVycm9yIFJhdGUnKQoKZy50eXAyID0gZ2dwbG90KGVyci5zdm0uZGlzdCkgKwogIGdlb21fY29sKG1hcHBpbmcgPSBhZXMoeCA9IHNvdXJjZSwgeSA9IHR5cDIsIGZpbGwgPSBzb3VyY2UpKSArCiAgZ2VvbV9lcnJvcmJhcihtYXBwaW5nID0gYWVzKHggPSBzb3VyY2UsIHltaW4gPSB0eXAyIC0gdHlwMi5lcnIsIHltYXggPSB0eXAyICsgdHlwMi5lcnIpLCB3aWR0aCA9IDAuNSkgKwogIGZhY2V0X3dyYXAofm1ldGhvZCwgbnJvdyA9IDEpICsKICBzY2FsZV9maWxsX21hbnVhbChsYWJlbHMgPSBjKCcwcGFyZXQnID0gJ1N0YXJ0aW5nIERhdGFzZXQnLCAnMWFkYXB0JyA9ICcrIEFkYXB0aXZlIFNhbXBsaW5nJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICcycmFuZG8nID0gJysgUmFuZG9tIFNhbXBsaW5nJyksIG5hbWUgPSAnJywKICAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGMoJzBwYXJldCcgPSAnc2t5Ymx1ZTInLCAnMWFkYXB0JyA9ICdyZWQnLCAnMnJhbmRvJyA9ICdncmVlbicpKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGV4cGFuc2lvbihtdWx0ID0gYygwLCAwLjA1KSkpICsgc2NhbGVfeF9kaXNjcmV0ZShicmVha3MgPSAnJykgKwogIHRoZW1lX2J3KCkgKyBsYWJzKHggPSAnJywgeSA9ICdGYWxzZSBOZWdhdGl2ZVxuRXJyb3IgUmF0ZScpCgooZy5yYXRlICsgZ3VpZGVzKGZpbGwgPSBGQUxTRSkpIC8gZy50eXAxIC8gKGcudHlwMiArIGd1aWRlcyhmaWxsID0gRkFMU0UpKQp3cml0ZS5jc3YoZXJyLnN2bS5kaXN0LCAnU1ZNX2Vycm9yX2RlbHRhLmNzdicsIHJvdy5uYW1lcyA9IEYpCnJtKGVyci5wYXJldCwgZXJyLmFkYXB0LCBlcnIucmFuZG8pCgpgYGAKCkdyYXBoaWNhbGx5LCB0aGUgc2VsZWN0aW9uIG9mIHRoZSBrZXJuZWwgZm9ybSBoYXMgYSBsYXJnZSBpbXBhY3Qgb24gdGhlIHF1YWxpdHkgb2YgdGhlIHJlc3VsdC4gClRoZSBwb2x5bm9taWFsIGFuZCByYWRpYWwga2VybmVscyBhcHBlYXIgdG8gcm91Z2hseSBtYXRjaCB0aGUgc2hhcGUgb2YgdGhlIG5vcm1hbGl6ZWQgZGlzdGFuY2UgPSAxIGNyaXRlcmlvbjsgCnRoZSBsaW5lYXIgYW5kIHNpZ21vaWRhbCBrZXJuZWxzIGRvIG5vdCBhcHBlYXIgdG8gbWF0Y2ggYW55dGhpbmcuIApGdXJ0aGVyIHR1bmluZyBvZiB0aGUgcG9seW5vbWlhbCBkZWdyZWUgb3IgY29lZmZpY2llbnQgY291bGQgaW1wcm92ZSB0aGUgcmVzdWx0cywgYnV0IGdpdmVuIHRoZSBjb25zdHJhaW5lZCBjb21wdXRhdGlvbmFsIGJ1ZGdldCBmb3Igc2FtcGxpbmcsIHNlcGFyYXRpb24gaW50byBhIHRyYWluaW5nIGFuZCB0ZXN0aW5nIHNldCB3b3VsZCBsaWtlbHkgbGVhZCB0byB3b3JzZSByZXN1bHRzLgoKCmBgYHtyIFNWTTogRGlzdGFuY2UgbWFyZ2luYWxzfQpzdm0ubWFyZ2luLnBhcmV0ID0gbWFyZ2luYWwobW9kID0gbW9kLnBhcmV0KTsgc3ZtLm1hcmdpbi5wYXJldCRjYXQgPSAnc3RhcnQnCnN2bS5tYXJnaW4uYWRhcHQgPSBtYXJnaW5hbChtb2QgPSBtb2QuYWRhcHQpOyBzdm0ubWFyZ2luLmFkYXB0JGNhdCA9ICdhZGFwdCcKc3ZtLm1hcmdpbi5yYW5kbyA9IG1hcmdpbmFsKG1vZCA9IG1vZC5yYW5kbyk7IHN2bS5tYXJnaW4ucmFuZG8kY2F0ID0gJ3JhbmRvJwpzdm0ubWFyZ2luID0gcmJpbmQoc3ZtLm1hcmdpbi5wYXJldCwgc3ZtLm1hcmdpbi5hZGFwdCwgc3ZtLm1hcmdpbi5yYW5kbykKcm0oc3ZtLm1hcmdpbi5wYXJldCwgc3ZtLm1hcmdpbi5hZGFwdCwgc3ZtLm1hcmdpbi5yYW5kbykKCndyaXRlLmNzdihzdm0ubWFyZ2luLCAnTWFyZ2luX1NWTV9kaXN0LmNzdicsIHJvdy5uYW1lcyA9IEYpCgpJbmZlci5wbHQgPSByZWFkLmNzdignLi4vRXhfUXVhcnRpYy9NYXJnaW5hbHNfZGVsdGEuY3N2JykKSW5mZXIucGx0ID0gSW5mZXIucGx0WywhbmFtZXMoSW5mZXIucGx0KSAlaW4lIGMoJ1gnKV0KIyBuYW1lcyhJbmZlci5wbHQpCkluZmVyLnBsdCRtZXRob2QgPSAnR1AnCgpnZ3Bsb3QoKSArCiAgZ2VvbV9wYXRoKGRhdGEgPSBmaWx0ZXIoSW5mZXIucGx0LCBjYXQgPT0gJ3RydScpLCBtYXBwaW5nID0gYWVzKHggPSB4LCB5ID0gcHJvYiAtIHBzZCwgY29sb3IgPSBjYXQpLCBsaW5ldHlwZSA9IDMpICsKICBnZW9tX3BhdGgoZGF0YSA9IGZpbHRlcihJbmZlci5wbHQsIGNhdCA9PSAndHJ1JyksIG1hcHBpbmcgPSBhZXMoeCA9IHgsIHkgPSBwcm9iICsgcHNkLCBjb2xvciA9IGNhdCksIGxpbmV0eXBlID0gMykgKwogIGdlb21fcGF0aChkYXRhID0gc3ZtLm1hcmdpbiwgbWFwcGluZyA9IGFlcyh4ID0geCwgeSA9IHByb2IsIGxpbmV0eXBlID0gbWV0aG9kLCBjb2xvciA9IGNhdCkpICsKICBmYWNldF93cmFwKH52YXIsIG5yb3cgPSAyKSArIHRoZW1lX2J3KCkgKwogIGxhYnMoeCA9ICdJbnB1dCBWYWx1ZScsIHkgPSAnQ29uZGl0aW9uYWwgUHJvYmFiaWxpdHknLCBsaW5ldHlwZSA9ICdTVk0gS2VybmVsJywgY29sb3IgPSAnJykgKwogIHNjYWxlX2NvbG9yX21hbnVhbChsYWJlbHMgPSBjKCd0cnUnID0gJ0V4cGVjdGVkIE1hcmdpbmFsJywgJ3N0YXJ0JyA9ICdTdGFydGluZyBEYXRhc2V0JywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnYWRhcHQnID0gJysgQWRhcHRpdmUgU2FtcGxpbmcnLCAncmFuZG8nID0gJysgUmFuZG9tIFNhbXBsaW5nJyksCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGMoJ3RydScgPSAnYmxhY2snLCAnc3RhcnQnID0gJ3NreWJsdWUyJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnYWRhcHQnID0gJ3JlZCcsICdyYW5kbycgPSAnZ3JlZW4nKSwKICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYygndHJ1JywgJ3N0YXJ0JywgJ2FkYXB0JywgJ3JhbmRvJykpICsKICBndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChsaW5ldHlwZSA9IGMoMywgMSwgMSwgMSkpKSkgKwogIHNjYWxlX2xpbmV0eXBlX2Rpc2NyZXRlKGxhYmVscyA9IGMoJ1NWTS1wb2wnID0gJ1BvbHlub21pYWwnLCAncmFkJyA9ICdTVk0tUmFkaWFsJykpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSkgKyBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCAxKSkKCmdncGxvdCgpICsKICBnZW9tX3BhdGgoZGF0YSA9IGZpbHRlcihJbmZlci5wbHQsIGNhdCA9PSAndHJ1JyksIG1hcHBpbmcgPSBhZXMoeCA9IHgsIHkgPSBwcm9iIC0gcHNkLCBjb2xvciA9IGNhdCksIGxpbmV0eXBlID0gMykgKwogIGdlb21fcGF0aChkYXRhID0gZmlsdGVyKEluZmVyLnBsdCwgY2F0ID09ICd0cnUnKSwgbWFwcGluZyA9IGFlcyh4ID0geCwgeSA9IHByb2IgKyBwc2QsIGNvbG9yID0gY2F0KSwgbGluZXR5cGUgPSAzKSArCiAgZ2VvbV9wYXRoKGRhdGEgPSBmaWx0ZXIoSW5mZXIucGx0LCBjYXQgIT0gJ3RydScpLCBtYXBwaW5nID0gYWVzKHggPSB4LCB5ID0gcHJvYiwgY29sb3IgPSBjYXQpKSArCiAgZmFjZXRfd3JhcCh2YXJ+LiwgbnJvdyA9IDIpICsgCiAgc2NhbGVfY29sb3JfbWFudWFsKGxhYmVscyA9IGMoJ3RydScgPSAnRXhwZWN0ZWQgTWFyZ2luYWwnLCAnc3RhcnQnID0gJ1N0YXJ0aW5nIERhdGFzZXQnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdhZGFwdCcgPSAnKyBBZGFwdGl2ZSBTYW1wbGluZycsICdyYW5kbycgPSAnKyBSYW5kb20gU2FtcGxpbmcnKSwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYygndHJ1JyA9ICdibGFjaycsICdzdGFydCcgPSAnc2t5Ymx1ZTInLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdhZGFwdCcgPSAncmVkJywgJ3JhbmRvJyA9ICdncmVlbicpLAogICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBjKCd0cnUnLCAnc3RhcnQnLCAnYWRhcHQnLCAncmFuZG8nKSkgKwogIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KGxpbmV0eXBlID0gYygzLCAxLCAxLCAxKSkpKSArCiAgbGFicyh4ID0gJycsIHkgPSAnUHJvYmFiaWxpdHkgb2YgQWNjZXB0YW5jZScsIHN1YnRpdGxlID0gZXhwcmVzc2lvbignUGFyZXRvIERpc3RhbmNlJyksIGNvbG9yID0gJycpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwLjA1KSkgKyBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSkgKwogIHRoZW1lX2J3KCkKCiMgQ2FsY3VsYXRlIGNvZWZmaWNpZW50cyBvZiBkZXRlcm1pbmF0aW9uOiBlYXNpZXIgY29tcGFyaXNvbgpjb2VmZGV0ID0gZGF0YS5mcmFtZShtZXRob2QgPSBjKHJlcCgnU1ZNLXBvbCcsIDMpLCByZXAoJ1NWTS1yYWQnLCAzKSwgcmVwKCdHUCcsIDMpKSwKICAgICAgICAgICBkYXRhc2V0ID0gYygnMHN0YXJ0JywgJzFhZGFwdCcsICcycmFuZG8nKSwKIGNvZWYgPSBjKGNvcih4ID0gZmlsdGVyKHN2bS5tYXJnaW4sIG1ldGhvZCA9PSAnU1ZNLXBvbCcsIGNhdCA9PSAnc3RhcnQnKSRwcm9iLCAKICAgIHkgPSBmaWx0ZXIoSW5mZXIucGx0LCBjYXQgPT0gJ3RydScpJHByb2IsIG1ldGhvZCA9ICdwZWFyc29uJyleMiwKICBjb3IoeCA9IGZpbHRlcihzdm0ubWFyZ2luLCBtZXRob2QgPT0gJ1NWTS1wb2wnLCBjYXQgPT0gJ2FkYXB0JykkcHJvYiwgCiAgICB5ID0gZmlsdGVyKEluZmVyLnBsdCwgY2F0ID09ICd0cnUnKSRwcm9iLCBtZXRob2QgPSAncGVhcnNvbicpXjIsCiAgY29yKHggPSBmaWx0ZXIoc3ZtLm1hcmdpbiwgbWV0aG9kID09ICdTVk0tcG9sJywgY2F0ID09ICdyYW5kbycpJHByb2IsIAogICAgeSA9IGZpbHRlcihJbmZlci5wbHQsIGNhdCA9PSAndHJ1JykkcHJvYiwgbWV0aG9kID0gJ3BlYXJzb24nKV4yLAogIGNvcih4ID0gZmlsdGVyKHN2bS5tYXJnaW4sIG1ldGhvZCA9PSAnU1ZNLXJhZCcsIGNhdCA9PSAnc3RhcnQnKSRwcm9iLCAKICAgIHkgPSBmaWx0ZXIoSW5mZXIucGx0LCBjYXQgPT0gJ3RydScpJHByb2IsIG1ldGhvZCA9ICdwZWFyc29uJyleMiwKICBjb3IoeCA9IGZpbHRlcihzdm0ubWFyZ2luLCBtZXRob2QgPT0gJ1NWTS1yYWQnLCBjYXQgPT0gJ2FkYXB0JykkcHJvYiwgCiAgICB5ID0gZmlsdGVyKEluZmVyLnBsdCwgY2F0ID09ICd0cnUnKSRwcm9iLCBtZXRob2QgPSAncGVhcnNvbicpXjIsCiAgY29yKHggPSBmaWx0ZXIoc3ZtLm1hcmdpbiwgbWV0aG9kID09ICdTVk0tcmFkJywgY2F0ID09ICdyYW5kbycpJHByb2IsIAogICAgeSA9IGZpbHRlcihJbmZlci5wbHQsIGNhdCA9PSAndHJ1JykkcHJvYiwgbWV0aG9kID0gJ3BlYXJzb24nKV4yLAogIGNvcih4ID0gZmlsdGVyKEluZmVyLnBsdCwgY2F0ID09ICdzdGFydCcpJHByb2IsIAogICAgeSA9IGMoZmlsdGVyKEluZmVyLnBsdCwgY2F0ID09ICd0cnUnLCB2YXIgPT0gJ3gxJykkcHJvYiwgZmlsdGVyKEluZmVyLnBsdCwgY2F0ID09ICd0cnUnLCB2YXIgPT0gJ3gyJykkcHJvYiksIAogICAgbWV0aG9kID0gJ3BlYXJzb24nKV4yLAogIGNvcih4ID0gZmlsdGVyKEluZmVyLnBsdCwgY2F0ID09ICdhZGFwdCcpJHByb2IsIAogICAgeSA9IGMoZmlsdGVyKEluZmVyLnBsdCwgY2F0ID09ICd0cnUnLCB2YXIgPT0gJ3gxJykkcHJvYiwgZmlsdGVyKEluZmVyLnBsdCwgY2F0ID09ICd0cnUnLCB2YXIgPT0gJ3gyJykkcHJvYiksIAogICAgbWV0aG9kID0gJ3BlYXJzb24nKV4yLAogIGNvcih4ID0gZmlsdGVyKEluZmVyLnBsdCwgY2F0ID09ICdyYW5kbycpJHByb2IsIAogICAgeSA9IGMoZmlsdGVyKEluZmVyLnBsdCwgY2F0ID09ICd0cnUnLCB2YXIgPT0gJ3gxJykkcHJvYiwgZmlsdGVyKEluZmVyLnBsdCwgY2F0ID09ICd0cnUnLCB2YXIgPT0gJ3gyJykkcHJvYiksIAogICAgbWV0aG9kID0gJ3BlYXJzb24nKV4yKSkKCmNvZWZkZXQKd3JpdGUuY3N2KGNvZWZkZXQsICdNYXJnaW5hbHNfQ29lZkRldF9kZWx0YS5jc3YnLCByb3cubmFtZXMgPSBGKQoKZ2dwbG90KGNvZWZkZXQpICsKICBnZW9tX2NvbChtYXBwaW5nID0gYWVzKHggPSBkYXRhc2V0LCBmaWxsID0gZGF0YXNldCwgeSA9IGNvZWYpKSsKICBmYWNldF9ncmlkKH5tZXRob2QpICsKICBsYWJzKHggPSAnJywgeSA9ICcxLVZhcmlhYmxlIE1hcmdpbmFsIENvZWZmaWNpZW50IG9mIERldGVybWluYXRpb24nLCBzdWJ0aXRsZSA9ICdQYXJldG8gRGlzdGFuY2UnKSArCiAgc2NhbGVfeF9kaXNjcmV0ZShicmVha3MgPSBjKCkpICsKICBzY2FsZV9maWxsX21hbnVhbChsYWJlbHMgPSBjKCcwc3RhcnQnID0gJ1N0YXJ0aW5nIERhdGFzZXQnLCAnMWFkYXB0JyA9ICcrIEFkYXB0aXZlIFNhbXBsaW5nJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICcycmFuZG8nID0gJysgUmFuZG9tIFNhbXBsaW5nJyksIG5hbWUgPSAnJywKICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBjKCcwc3RhcnQnID0gJ3NreWJsdWUyJywgJzFhZGFwdCcgPSAncmVkJywgJzJyYW5kbycgPSAnZ3JlZW4nKSkgKwogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBleHBhbnNpb24obXVsdCA9IGMoMCwgMC4wNSkpKQoKYGBgCgpTaW5nbGUgdmFyaWFibGUgbWFyZ2luYWxzIGFyZSB2aXN1YWxseSBzaW1pbGFyIHRvIHRoZSBleHBlY3RlZCB2YWx1ZSwgYnV0IGNhbGN1bGF0aW5nIHRoZSBjb2VmZmljaWVudCBvZiBkZXRlcm1pbmF0aW9uIHNob3dzIHRoYXQgdGhlIEdQIG1ldGhvZCBpcyBtb3JlIGNvbnNpc3RlbnQgcmVnYXJkbGVzcyBvZiBkYXRhc2V0IGFuZCBpdCBkZW1vbnN0cmF0ZXMgdGhlIGV4cGVjdGVkIGltcHJvdmVtZW50IHdpdGggaW5jcmVhc2VkIHNhbXBsaW5nIG5lYXIgdGhlIGJvdW5kYXJ5LgpUaGUgU1ZNIG1ldGhvZHMgb3ZlcmFsbCBjYW5ub3QgY2FwdHVyZSB0aGUgYm91bmRhcnkgdmVyeSB3ZWxsLCBsZWFkaW5nIHRvIGNsYXNzaWZpY2F0aW9uIGVycm9ycy4KCmBgYHtyIFNWTTogVGhyZXNob2xkfQpkYXRhLnBhcmV0ID0gcmVhZC5jc3YoJy4uL0V4X1F1YXJ0aWMvR1Bhcl9hbGxfc3RhcnQuY3N2JykKZGF0YS5jdXRvZiA9IHJlYWQuY3N2KCcuLi9FeF9RdWFydGljL0dQYXJfQWNjZXB0X1RocmVzaG9sZC5jc3YnKQpkYXRhLnJhbmRvID0gcmVhZC5jc3YoZmlsZSA9ICdHUGFyX1JhbmRvbS5jc3YnKQpkYXRhLnJhbmRvID0gZGF0YS5yYW5kb1sxOm5yb3coZGF0YS5wYXJldCksIF0KCiMgRGVmaW5lIGFjY2VwdGFuY2UKZGF0YS5wYXJldCRjYXQgPSAwOyBkYXRhLmN1dG9mJGNhdCA9IDA7IGRhdGEucmFuZG8kY2F0ID0gMDsgZmluZS5ncmlkJGNhdCA9IDAKZGF0YS5wYXJldCRjYXRbZGF0YS5wYXJldCRmMS5ub3JtIDw9IDEgJiBkYXRhLnBhcmV0JGYyLm5vcm0gPD0gMV0gPSAxCmRhdGEuY3V0b2YkY2F0W2RhdGEuY3V0b2YkZjEubm9ybSA8PSAxICYgZGF0YS5jdXRvZiRmMi5ub3JtIDw9IDFdID0gMQpkYXRhLnJhbmRvJGNhdFtkYXRhLnJhbmRvJGYxLm5vcm0gPD0gMSAmIGRhdGEucmFuZG8kZjIubm9ybSA8PSAxXSA9IDEKZmluZS5ncmlkJGNhdFtmaW5lLmdyaWQkZjEubm9ybSA8PSAxICYgZmluZS5ncmlkJGYyLm5vcm0gPD0gMV0gPSAxCgojIFBsb3RzCm1vZC5wYXJldCA9IFNWTS5tb2QuYWxsKGlucHV0LmRhdGEgPSBkYXRhLnBhcmV0KQpTVk0uaW5wdXQoZmluZS5pbnB1dCA9IGZpbmUuZ3JpZFssYygneDEnLCAneDInLCAnY2F0JyldLCAKICAgICAgICAgIG1vZC5yYWQgPSBtb2QucGFyZXQkcmFkLCBtb2QubGluID0gbW9kLnBhcmV0JGxpbiwgCiAgICAgICAgICBtb2QucG9sID0gbW9kLnBhcmV0JHBvbCwgbW9kLnNpZyA9IG1vZC5wYXJldCRzaWcsCiAgICAgICAgICB0aXQgPSAnVGhyZXNob2xkIEN1dG9mZiwgU3RhcnRpbmcgRGF0YXNldCcpCgptb2QuYWRhcHQgPSBTVk0ubW9kLmFsbChpbnB1dC5kYXRhID0gZGF0YS5jdXRvZikKbW9kLmFkYXB0LmN1dG9mID0gbW9kLmFkYXB0ClNWTS5pbnB1dChmaW5lLmlucHV0ID0gZmluZS5ncmlkWyxjKCd4MScsICd4MicsICdjYXQnKV0sIAogICAgICAgICAgbW9kLnJhZCA9IG1vZC5hZGFwdCRyYWQsIG1vZC5saW4gPSBtb2QuYWRhcHQkbGluLCAKICAgICAgICAgIG1vZC5wb2wgPSBtb2QuYWRhcHQkcG9sLCBtb2Quc2lnID0gbW9kLmFkYXB0JHNpZywKICAgICAgICAgIHRpdCA9ICdUaHJlc2hvbGQgQ3V0b2ZmLCArIEFkYXB0aXZlIFNhbXBsaW5nJykKCm1vZC5yYW5kbyA9IFNWTS5tb2QuYWxsKGlucHV0LmRhdGEgPSBkYXRhLnJhbmRvKQpTVk0uaW5wdXQoZmluZS5pbnB1dCA9IGZpbmUuZ3JpZFssYygneDEnLCAneDInLCAnY2F0JyldLCAKICAgICAgICAgIG1vZC5yYWQgPSBtb2QucmFuZG8kcmFkLCBtb2QubGluID0gbW9kLnJhbmRvJGxpbiwgCiAgICAgICAgICBtb2QucG9sID0gbW9kLnJhbmRvJHBvbCwgbW9kLnNpZyA9IG1vZC5yYW5kbyRzaWcsCiAgICAgICAgICB0aXQgPSAnVGhyZXNob2xkIEN1dG9mZiwgKyBSYW5kb20gU2FtcGxpbmcnKQoKIyBFcnJvciByYXRlcwplcnIucGFyZXQgPSBTVk0uZXJyKGZpbmUuaW5wdXQgPSBmaW5lLmdyaWQsIG1vZC5saXN0ID0gbW9kLnBhcmV0KQplcnIucGFyZXQkc291cmNlID0gJzBwYXJldCc7IGVyci5wYXJldCRjcml0ZXJpYSA9ICdUaHJlc2hvbGQgQ3V0b2ZmJwplcnIuYWRhcHQgPSBTVk0uZXJyKGZpbmUuaW5wdXQgPSBmaW5lLmdyaWQsIG1vZC5saXN0ID0gbW9kLmFkYXB0KQplcnIuYWRhcHQkc291cmNlID0gJzFhZGFwdCc7IGVyci5hZGFwdCRjcml0ZXJpYSA9ICdUaHJlc2hvbGQgQ3V0b2ZmJwplcnIucmFuZG8gPSBTVk0uZXJyKGZpbmUuaW5wdXQgPSBmaW5lLmdyaWQsIG1vZC5saXN0ID0gbW9kLnJhbmRvKQplcnIucmFuZG8kc291cmNlID0gJzJyYW5kbyc7IGVyci5yYW5kbyRjcml0ZXJpYSA9ICdUaHJlc2hvbGQgQ3V0b2ZmJwoKZXJyLnN2bS5jdXRvZiA9IHJiaW5kKGVyci5wYXJldCwgZXJyLmFkYXB0LCBlcnIucmFuZG8pCgpnLnJhdGUgPSBnZ3Bsb3QoZXJyLnN2bS5jdXRvZikgKwogIGdlb21fY29sKG1hcHBpbmcgPSBhZXMoeCA9IHNvdXJjZSwgeSA9IHJhdGUsIGZpbGwgPSBzb3VyY2UpKSArCiAgZ2VvbV9lcnJvcmJhcihtYXBwaW5nID0gYWVzKHggPSBzb3VyY2UsIHltaW4gPSByYXRlIC0gZXJyLCB5bWF4ID0gcmF0ZSArIGVyciksIHdpZHRoID0gMC41KSArCiAgZmFjZXRfd3JhcCh+bWV0aG9kLCBucm93ID0gMSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKGxhYmVscyA9IGMoJzBwYXJldCcgPSAnU3RhcnRpbmcgRGF0YXNldCcsICcxYWRhcHQnID0gJysgQWRhcHRpdmUgU2FtcGxpbmcnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJzJyYW5kbycgPSAnKyBSYW5kb20gU2FtcGxpbmcnKSwgbmFtZSA9ICcnLAogICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYygnMHBhcmV0JyA9ICdza3libHVlMicsICcxYWRhcHQnID0gJ3JlZCcsICcycmFuZG8nID0gJ2dyZWVuJykpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gZXhwYW5zaW9uKG11bHQgPSBjKDAsIDAuMDUpKSkgKyBzY2FsZV94X2Rpc2NyZXRlKGJyZWFrcyA9ICcnKSArCiAgdGhlbWVfYncoKSArIGxhYnMoeCA9ICcnLCB5ID0gJ1RvdGFsXG5FcnJvciBSYXRlJywgc3VidGl0bGUgPSAnQ3V0b2ZmIFRocmVzaG9sZCcpCgpnLnR5cDEgPSBnZ3Bsb3QoZXJyLnN2bS5jdXRvZikgKwogIGdlb21fY29sKG1hcHBpbmcgPSBhZXMoeCA9IHNvdXJjZSwgeSA9IHR5cDEsIGZpbGwgPSBzb3VyY2UpKSArCiAgZ2VvbV9lcnJvcmJhcihtYXBwaW5nID0gYWVzKHggPSBzb3VyY2UsIHltaW4gPSB0eXAxIC0gdHlwMS5lcnIsIHltYXggPSB0eXAxICsgdHlwMS5lcnIpLCB3aWR0aCA9IDAuNSkgKwogIGZhY2V0X3dyYXAofm1ldGhvZCwgbnJvdyA9IDEpICsKICBzY2FsZV9maWxsX21hbnVhbChsYWJlbHMgPSBjKCcwcGFyZXQnID0gJ1N0YXJ0aW5nIERhdGFzZXQnLCAnMWFkYXB0JyA9ICcrIEFkYXB0aXZlIFNhbXBsaW5nJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICcycmFuZG8nID0gJysgUmFuZG9tIFNhbXBsaW5nJyksIG5hbWUgPSAnJywKICAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGMoJzBwYXJldCcgPSAnc2t5Ymx1ZTInLCAnMWFkYXB0JyA9ICdyZWQnLCAnMnJhbmRvJyA9ICdncmVlbicpKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGV4cGFuc2lvbihtdWx0ID0gYygwLCAwLjA1KSkpICsgc2NhbGVfeF9kaXNjcmV0ZShicmVha3MgPSAnJykgKwogIHRoZW1lX2J3KCkgKyBsYWJzKHggPSAnJywgeSA9ICdGYWxzZSBQb3NpdGl2ZVxuRXJyb3IgUmF0ZScpCgpnLnR5cDIgPSBnZ3Bsb3QoZXJyLnN2bS5jdXRvZikgKwogIGdlb21fY29sKG1hcHBpbmcgPSBhZXMoeCA9IHNvdXJjZSwgeSA9IHR5cDIsIGZpbGwgPSBzb3VyY2UpKSArCiAgZ2VvbV9lcnJvcmJhcihtYXBwaW5nID0gYWVzKHggPSBzb3VyY2UsIHltaW4gPSB0eXAyIC0gdHlwMi5lcnIsIHltYXggPSB0eXAyICsgdHlwMi5lcnIpLCB3aWR0aCA9IDAuNSkgKwogIGZhY2V0X3dyYXAofm1ldGhvZCwgbnJvdyA9IDEpICsKICBzY2FsZV9maWxsX21hbnVhbChsYWJlbHMgPSBjKCcwcGFyZXQnID0gJ1N0YXJ0aW5nIERhdGFzZXQnLCAnMWFkYXB0JyA9ICcrIEFkYXB0aXZlIFNhbXBsaW5nJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICcycmFuZG8nID0gJysgUmFuZG9tIFNhbXBsaW5nJyksIG5hbWUgPSAnJywKICAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGMoJzBwYXJldCcgPSAnc2t5Ymx1ZTInLCAnMWFkYXB0JyA9ICdyZWQnLCAnMnJhbmRvJyA9ICdncmVlbicpKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGV4cGFuc2lvbihtdWx0ID0gYygwLCAwLjA1KSkpICsgc2NhbGVfeF9kaXNjcmV0ZShicmVha3MgPSAnJykgKwogIHRoZW1lX2J3KCkgKyBsYWJzKHggPSAnJywgeSA9ICdGYWxzZSBOZWdhdGl2ZVxuRXJyb3IgUmF0ZScpCgooZy5yYXRlICsgZ3VpZGVzKGZpbGwgPSBGQUxTRSkpIC8gZy50eXAxIC8gKGcudHlwMiArIGd1aWRlcyhmaWxsID0gRkFMU0UpKQp3cml0ZS5jc3YoZXJyLnN2bS5jdXRvZiwgJ1NWTV9lcnJvcl9jdXRvZi5jc3YnLCByb3cubmFtZXMgPSBGKQoKcm0oZXJyLnBhcmV0LCBlcnIuYWRhcHQsIGVyci5yYW5kbywgZy5yYXRlLCBnLnR5cDEsIGcudHlwMikKCmBgYAoKYGBge3IgU1ZNIE1hcmdpbmFsaXphdGlvbjogVGhyZXNob2xkIEN1dG9mZn0Kc3ZtLm1hcmdpbi5wYXJldCA9IG1hcmdpbmFsKG1vZCA9IG1vZC5wYXJldCk7IHN2bS5tYXJnaW4ucGFyZXQkY2F0ID0gJ3N0YXJ0Jwpzdm0ubWFyZ2luLmFkYXB0ID0gbWFyZ2luYWwobW9kID0gbW9kLmFkYXB0KTsgc3ZtLm1hcmdpbi5hZGFwdCRjYXQgPSAnYWRhcHQnCnN2bS5tYXJnaW4ucmFuZG8gPSBtYXJnaW5hbChtb2QgPSBtb2QucmFuZG8pOyBzdm0ubWFyZ2luLnJhbmRvJGNhdCA9ICdyYW5kbycKc3ZtLm1hcmdpbiA9IHJiaW5kKHN2bS5tYXJnaW4ucGFyZXQsIHN2bS5tYXJnaW4uYWRhcHQsIHN2bS5tYXJnaW4ucmFuZG8pCnJtKHN2bS5tYXJnaW4ucGFyZXQsIHN2bS5tYXJnaW4uYWRhcHQsIHN2bS5tYXJnaW4ucmFuZG8pCgp3cml0ZS5jc3Yoc3ZtLm1hcmdpbiwgJ01hcmdpbl9TVk1fY3V0b2YuY3N2Jywgcm93Lm5hbWVzID0gRikKCkluZmVyLnBsdCA9IHJlYWQuY3N2KCcuLi9FeF9RdWFydGljL01hcmdpbmFsc19jdXRvZi5jc3YnKQpJbmZlci5wbHQgPSBJbmZlci5wbHRbLCFuYW1lcyhJbmZlci5wbHQpICVpbiUgYygnWCcpXQojIG5hbWVzKEluZmVyLnBsdCkKSW5mZXIucGx0JG1ldGhvZCA9ICdHUCcKCmdncGxvdCgpICsKICBnZW9tX3BhdGgoZGF0YSA9IGZpbHRlcihJbmZlci5wbHQsIGNhdCA9PSAndHJ1JyksIG1hcHBpbmcgPSBhZXMoeCA9IHgsIHkgPSBwcm9iIC0gcHNkLCBjb2xvciA9IGNhdCksIGxpbmV0eXBlID0gMykgKwogIGdlb21fcGF0aChkYXRhID0gZmlsdGVyKEluZmVyLnBsdCwgY2F0ID09ICd0cnUnKSwgbWFwcGluZyA9IGFlcyh4ID0geCwgeSA9IHByb2IgKyBwc2QsIGNvbG9yID0gY2F0KSwgbGluZXR5cGUgPSAzKSArCiAgZ2VvbV9wYXRoKGRhdGEgPSBzdm0ubWFyZ2luLCBtYXBwaW5nID0gYWVzKHggPSB4LCB5ID0gcHJvYiwgbGluZXR5cGUgPSBtZXRob2QsIGNvbG9yID0gY2F0KSkgKwogIGZhY2V0X3dyYXAofnZhciwgbnJvdyA9IDIpICsgdGhlbWVfYncoKSArCiAgbGFicyh4ID0gJ0lucHV0IFZhbHVlJywgeSA9ICdDb25kaXRpb25hbCBQcm9iYWJpbGl0eScsIGxpbmV0eXBlID0gJ1NWTSBLZXJuZWwnLCBjb2xvciA9ICcnLCBzdWJ0aXRsZSA9ICdDdXRvZmYgVGhyZXNob2xkJykgKwogIHNjYWxlX2NvbG9yX21hbnVhbChsYWJlbHMgPSBjKCd0cnUnID0gJ0V4cGVjdGVkIE1hcmdpbmFsJywgJ3N0YXJ0JyA9ICdTdGFydGluZyBEYXRhc2V0JywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnYWRhcHQnID0gJysgQWRhcHRpdmUgU2FtcGxpbmcnLCAncmFuZG8nID0gJysgUmFuZG9tIFNhbXBsaW5nJyksCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGMoJ3RydScgPSAnYmxhY2snLCAnc3RhcnQnID0gJ3NreWJsdWUyJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnYWRhcHQnID0gJ3JlZCcsICdyYW5kbycgPSAnZ3JlZW4nKSwKICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYygndHJ1JywgJ3N0YXJ0JywgJ2FkYXB0JywgJ3JhbmRvJykpICsKICBndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChsaW5ldHlwZSA9IGMoMywgMSwgMSwgMSkpKSkgKwogIHNjYWxlX2xpbmV0eXBlX2Rpc2NyZXRlKGxhYmVscyA9IGMoJ1NWTS1wb2wnID0gJ1BvbHlub21pYWwnLCAncmFkJyA9ICdTVk0tUmFkaWFsJykpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSkgKyBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCAxKSkKCmdncGxvdCgpICsKICBnZW9tX3BhdGgoZGF0YSA9IGZpbHRlcihJbmZlci5wbHQsIGNhdCA9PSAndHJ1JyksIG1hcHBpbmcgPSBhZXMoeCA9IHgsIHkgPSBwcm9iIC0gcHNkLCBjb2xvciA9IGNhdCksIGxpbmV0eXBlID0gMykgKwogIGdlb21fcGF0aChkYXRhID0gZmlsdGVyKEluZmVyLnBsdCwgY2F0ID09ICd0cnUnKSwgbWFwcGluZyA9IGFlcyh4ID0geCwgeSA9IHByb2IgKyBwc2QsIGNvbG9yID0gY2F0KSwgbGluZXR5cGUgPSAzKSArCiAgZ2VvbV9wYXRoKGRhdGEgPSBmaWx0ZXIoSW5mZXIucGx0LCBjYXQgIT0gJ3RydScpLCBtYXBwaW5nID0gYWVzKHggPSB4LCB5ID0gcHJvYiwgY29sb3IgPSBjYXQpKSArCiAgZmFjZXRfd3JhcCh2YXJ+LiwgbnJvdyA9IDIpICsgCiAgc2NhbGVfY29sb3JfbWFudWFsKGxhYmVscyA9IGMoJ3RydScgPSAnRXhwZWN0ZWQgTWFyZ2luYWwnLCAnc3RhcnQnID0gJ1N0YXJ0aW5nIERhdGFzZXQnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdhZGFwdCcgPSAnKyBBZGFwdGl2ZSBTYW1wbGluZycsICdyYW5kbycgPSAnKyBSYW5kb20gU2FtcGxpbmcnKSwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYygndHJ1JyA9ICdibGFjaycsICdzdGFydCcgPSAnc2t5Ymx1ZTInLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdhZGFwdCcgPSAncmVkJywgJ3JhbmRvJyA9ICdncmVlbicpLAogICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBjKCd0cnUnLCAnc3RhcnQnLCAnYWRhcHQnLCAncmFuZG8nKSkgKwogIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KGxpbmV0eXBlID0gYygzLCAxLCAxLCAxKSkpKSArCiAgbGFicyh4ID0gJycsIHkgPSAnUHJvYmFiaWxpdHkgb2YgQWNjZXB0YW5jZScsIHN1YnRpdGxlID0gJ0N1dG9mZiBUaHJlc2hvbGQnLCBjb2xvciA9ICcnKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMC4wNSkpICsgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCkpICsKICB0aGVtZV9idygpCgojIENhbGN1bGF0ZSBjb2VmZmljaWVudHMgb2YgZGV0ZXJtaW5hdGlvbjogZWFzaWVyIGNvbXBhcmlzb24KY29lZmRldCA9IGRhdGEuZnJhbWUobWV0aG9kID0gYyhyZXAoJ1NWTS1wb2wnLCAzKSwgcmVwKCdTVk0tcmFkJywgMyksIHJlcCgnR1AnLCAzKSksCiAgICAgICAgICAgZGF0YXNldCA9IGMoJzBzdGFydCcsICcxYWRhcHQnLCAnMnJhbmRvJyksCiBjb2VmID0gYyhjb3IoeCA9IGZpbHRlcihzdm0ubWFyZ2luLCBtZXRob2QgPT0gJ1NWTS1wb2wnLCBjYXQgPT0gJ3N0YXJ0JykkcHJvYiwgCiAgICB5ID0gZmlsdGVyKEluZmVyLnBsdCwgY2F0ID09ICd0cnUnKSRwcm9iLCBtZXRob2QgPSAncGVhcnNvbicpXjIsCiAgY29yKHggPSBmaWx0ZXIoc3ZtLm1hcmdpbiwgbWV0aG9kID09ICdTVk0tcG9sJywgY2F0ID09ICdhZGFwdCcpJHByb2IsIAogICAgeSA9IGZpbHRlcihJbmZlci5wbHQsIGNhdCA9PSAndHJ1JykkcHJvYiwgbWV0aG9kID0gJ3BlYXJzb24nKV4yLAogIGNvcih4ID0gZmlsdGVyKHN2bS5tYXJnaW4sIG1ldGhvZCA9PSAnU1ZNLXBvbCcsIGNhdCA9PSAncmFuZG8nKSRwcm9iLCAKICAgIHkgPSBmaWx0ZXIoSW5mZXIucGx0LCBjYXQgPT0gJ3RydScpJHByb2IsIG1ldGhvZCA9ICdwZWFyc29uJyleMiwKICBjb3IoeCA9IGZpbHRlcihzdm0ubWFyZ2luLCBtZXRob2QgPT0gJ1NWTS1yYWQnLCBjYXQgPT0gJ3N0YXJ0JykkcHJvYiwgCiAgICB5ID0gZmlsdGVyKEluZmVyLnBsdCwgY2F0ID09ICd0cnUnKSRwcm9iLCBtZXRob2QgPSAncGVhcnNvbicpXjIsCiAgY29yKHggPSBmaWx0ZXIoc3ZtLm1hcmdpbiwgbWV0aG9kID09ICdTVk0tcmFkJywgY2F0ID09ICdhZGFwdCcpJHByb2IsIAogICAgeSA9IGZpbHRlcihJbmZlci5wbHQsIGNhdCA9PSAndHJ1JykkcHJvYiwgbWV0aG9kID0gJ3BlYXJzb24nKV4yLAogIGNvcih4ID0gZmlsdGVyKHN2bS5tYXJnaW4sIG1ldGhvZCA9PSAnU1ZNLXJhZCcsIGNhdCA9PSAncmFuZG8nKSRwcm9iLCAKICAgIHkgPSBmaWx0ZXIoSW5mZXIucGx0LCBjYXQgPT0gJ3RydScpJHByb2IsIG1ldGhvZCA9ICdwZWFyc29uJyleMiwKICBjb3IoeCA9IGZpbHRlcihJbmZlci5wbHQsIGNhdCA9PSAnc3RhcnQnKSRwcm9iLCAKICAgIHkgPSBjKGZpbHRlcihJbmZlci5wbHQsIGNhdCA9PSAndHJ1JywgdmFyID09ICd4MScpJHByb2IsIGZpbHRlcihJbmZlci5wbHQsIGNhdCA9PSAndHJ1JywgdmFyID09ICd4MicpJHByb2IpLCAKICAgIG1ldGhvZCA9ICdwZWFyc29uJyleMiwKICBjb3IoeCA9IGZpbHRlcihJbmZlci5wbHQsIGNhdCA9PSAnYWRhcHQnKSRwcm9iLCAKICAgIHkgPSBjKGZpbHRlcihJbmZlci5wbHQsIGNhdCA9PSAndHJ1JywgdmFyID09ICd4MScpJHByb2IsIGZpbHRlcihJbmZlci5wbHQsIGNhdCA9PSAndHJ1JywgdmFyID09ICd4MicpJHByb2IpLCAKICAgIG1ldGhvZCA9ICdwZWFyc29uJyleMiwKICBjb3IoeCA9IGZpbHRlcihJbmZlci5wbHQsIGNhdCA9PSAncmFuZG8nKSRwcm9iLCAKICAgIHkgPSBjKGZpbHRlcihJbmZlci5wbHQsIGNhdCA9PSAndHJ1JywgdmFyID09ICd4MScpJHByb2IsIGZpbHRlcihJbmZlci5wbHQsIGNhdCA9PSAndHJ1JywgdmFyID09ICd4MicpJHByb2IpLCAKICAgIG1ldGhvZCA9ICdwZWFyc29uJyleMikpCgpjb2VmZGV0CndyaXRlLmNzdihjb2VmZGV0LCAnTWFyZ2luYWxzX0NvZWZEZXRfY3V0b2YuY3N2Jywgcm93Lm5hbWVzID0gRikKCmdncGxvdChjb2VmZGV0KSArCiAgZ2VvbV9jb2wobWFwcGluZyA9IGFlcyh4ID0gZGF0YXNldCwgZmlsbCA9IGRhdGFzZXQsIHkgPSBjb2VmKSkrCiAgZmFjZXRfZ3JpZCh+bWV0aG9kKSArCiAgbGFicyh4ID0gJycsIHkgPSAnMS1WYXJpYWJsZSBNYXJnaW5hbCBDb2VmZmljaWVudCBvZiBEZXRlcm1pbmF0aW9uJywgc3VidGl0bGUgPSAnQ3V0b2ZmIFRocmVzaG9sZCcpICsKICBzY2FsZV94X2Rpc2NyZXRlKGJyZWFrcyA9IGMoKSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKGxhYmVscyA9IGMoJzBzdGFydCcgPSAnU3RhcnRpbmcgRGF0YXNldCcsICcxYWRhcHQnID0gJysgQWRhcHRpdmUgU2FtcGxpbmcnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJzJyYW5kbycgPSAnKyBSYW5kb20gU2FtcGxpbmcnKSwgbmFtZSA9ICcnLAogICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGMoJzBzdGFydCcgPSAnc2t5Ymx1ZTInLCAnMWFkYXB0JyA9ICdyZWQnLCAnMnJhbmRvJyA9ICdncmVlbicpKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGV4cGFuc2lvbihtdWx0ID0gYygwLCAwLjA1KSkpCgpgYGAKCmBgYHtyIFNWTTogUmFkaXVzIGFuZCBQcmlvcml0eX0KZGF0YS5wYXJldCA9IHJlYWQuY3N2KGZpbGUgPSAnLi4vRXhfUXVhcnRpYy9HUGFyX2FsbF9zdGFydC5jc3YnKQpkYXRhLnJhZGFuID0gcmVhZC5jc3YoJy4uL0V4X1F1YXJ0aWMvR1Bhcl9BY2NlcHRfUmFkaXVzLmNzdicpCmRhdGEucmFuZG8gPSByZWFkLmNzdihmaWxlID0gJ0dQYXJfUmFuZG9tLmNzdicpCmRhdGEucmFuZG8gPSBkYXRhLnJhbmRvWzE6bnJvdyhkYXRhLnBhcmV0KSwgXQoKIyBEZWZpbmUgYWNjZXB0YW5jZQpkYXRhLnBhcmV0JGNhdCA9IDA7IGRhdGEucmFkYW4kY2F0ID0gMDsgZGF0YS5yYW5kbyRjYXQgPSAwOyBmaW5lLmdyaWQkY2F0ID0gMApkYXRhLnBhcmV0JGNhdFtzcXJ0KGRhdGEucGFyZXQkZjEubm9ybV4yICsgZGF0YS5wYXJldCRmMi5ub3JtXjIpIDw9IDEgJiBkYXRhLnBhcmV0JHRoZXRhID4gMjBdID0gMQpkYXRhLnJhZGFuJGNhdFtzcXJ0KGRhdGEucmFkYW4kZjEubm9ybV4yICsgZGF0YS5yYWRhbiRmMi5ub3JtXjIpIDw9IDEgJiBkYXRhLnJhZGFuJHRoZXRhID4gMjBdID0gMQpkYXRhLnJhbmRvJGNhdFtzcXJ0KGRhdGEucmFuZG8kZjEubm9ybV4yICsgZGF0YS5yYW5kbyRmMi5ub3JtXjIpIDw9IDEgJiBkYXRhLnJhbmRvJHRoZXRhID4gMjBdID0gMQpmaW5lLmdyaWQkY2F0W3NxcnQoZmluZS5ncmlkJGYxLm5vcm1eMiArIGZpbmUuZ3JpZCRmMi5ub3JtXjIpIDw9IDEgJiBmaW5lLmdyaWQkYW5nID4gMjBdID0gMQoKIyBQbG90cwptb2QucGFyZXQgPSBTVk0ubW9kLmFsbChpbnB1dC5kYXRhID0gZGF0YS5wYXJldCkKU1ZNLmlucHV0KGZpbmUuaW5wdXQgPSBmaW5lLmdyaWRbLGMoJ3gxJywgJ3gyJywgJ2NhdCcpXSwgCiAgICAgICAgICBtb2QucmFkID0gbW9kLnBhcmV0JHJhZCwgbW9kLmxpbiA9IG1vZC5wYXJldCRsaW4sIAogICAgICAgICAgbW9kLnBvbCA9IG1vZC5wYXJldCRwb2wsIG1vZC5zaWcgPSBtb2QucGFyZXQkc2lnLAogICAgICAgICAgdGl0ID0gJ1V0b3BpYSBEaXN0YW5jZSArIFByaW9yaXR5LCBTdGFydGluZyBEYXRhc2V0JykKCm1vZC5hZGFwdCA9IFNWTS5tb2QuYWxsKGlucHV0LmRhdGEgPSBkYXRhLnJhZGFuKQptb2QuYWRhcHQucmFkYW4gPSBtb2QuYWRhcHQKU1ZNLmlucHV0KGZpbmUuaW5wdXQgPSBmaW5lLmdyaWRbLGMoJ3gxJywgJ3gyJywgJ2NhdCcpXSwgCiAgICAgICAgICBtb2QucmFkID0gbW9kLmFkYXB0JHJhZCwgbW9kLmxpbiA9IG1vZC5hZGFwdCRsaW4sIAogICAgICAgICAgbW9kLnBvbCA9IG1vZC5hZGFwdCRwb2wsIG1vZC5zaWcgPSBtb2QuYWRhcHQkc2lnLAogICAgICAgICAgdGl0ID0gJ1V0b3BpYSBEaXN0YW5jZSArIFByaW9yaXR5LCArIEFkYXB0aXZlIFNhbXBsaW5nJykKCm1vZC5yYW5kbyA9IFNWTS5tb2QuYWxsKGlucHV0LmRhdGEgPSBkYXRhLnJhbmRvKQpTVk0uaW5wdXQoZmluZS5pbnB1dCA9IGZpbmUuZ3JpZFssYygneDEnLCAneDInLCAnY2F0JyldLCAKICAgICAgICAgIG1vZC5yYWQgPSBtb2QucmFuZG8kcmFkLCBtb2QubGluID0gbW9kLnJhbmRvJGxpbiwgCiAgICAgICAgICBtb2QucG9sID0gbW9kLnJhbmRvJHBvbCwgbW9kLnNpZyA9IG1vZC5yYW5kbyRzaWcsCiAgICAgICAgICB0aXQgPSAnVXRvcGlhIERpc3RhbmNlICsgUHJpb3JpdHksICsgUmFuZG9tIFNhbXBsaW5nJykKCiMgRXJyb3IgcmF0ZXMKZXJyLnBhcmV0ID0gU1ZNLmVycihmaW5lLmlucHV0ID0gZmluZS5ncmlkLCBtb2QubGlzdCA9IG1vZC5wYXJldCkKZXJyLnBhcmV0JHNvdXJjZSA9ICcwcGFyZXQnOyBlcnIucGFyZXQkY3JpdGVyaWEgPSAnVXRvcGlhIERpc3RhbmNlJwplcnIuYWRhcHQgPSBTVk0uZXJyKGZpbmUuaW5wdXQgPSBmaW5lLmdyaWQsIG1vZC5saXN0ID0gbW9kLmFkYXB0KQplcnIuYWRhcHQkc291cmNlID0gJzFhZGFwdCc7IGVyci5hZGFwdCRjcml0ZXJpYSA9ICdVdG9waWEgRGlzdGFuY2UnCmVyci5yYW5kbyA9IFNWTS5lcnIoZmluZS5pbnB1dCA9IGZpbmUuZ3JpZCwgbW9kLmxpc3QgPSBtb2QucmFuZG8pCmVyci5yYW5kbyRzb3VyY2UgPSAnMnJhbmRvJzsgZXJyLnJhbmRvJGNyaXRlcmlhID0gJ1V0b3BpYSBEaXN0YW5jZScKCmVyci5zdm0ucmFkYW4gPSByYmluZChlcnIucGFyZXQsIGVyci5hZGFwdCwgZXJyLnJhbmRvKQoKZy5yYXRlID0gZ2dwbG90KGVyci5zdm0ucmFkYW4pICsKICBnZW9tX2NvbChtYXBwaW5nID0gYWVzKHggPSBzb3VyY2UsIHkgPSByYXRlLCBmaWxsID0gc291cmNlKSkgKwogIGdlb21fZXJyb3JiYXIobWFwcGluZyA9IGFlcyh4ID0gc291cmNlLCB5bWluID0gcmF0ZSAtIGVyciwgeW1heCA9IHJhdGUgKyBlcnIpLCB3aWR0aCA9IDAuNSkgKwogIGZhY2V0X3dyYXAofm1ldGhvZCwgbnJvdyA9IDEpICsKICBzY2FsZV9maWxsX21hbnVhbChsYWJlbHMgPSBjKCcwcGFyZXQnID0gJ1N0YXJ0aW5nIERhdGFzZXQnLCAnMWFkYXB0JyA9ICcrIEFkYXB0aXZlIFNhbXBsaW5nJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICcycmFuZG8nID0gJysgUmFuZG9tIFNhbXBsaW5nJyksIG5hbWUgPSAnJywKICAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGMoJzBwYXJldCcgPSAnc2t5Ymx1ZTInLCAnMWFkYXB0JyA9ICdyZWQnLCAnMnJhbmRvJyA9ICdncmVlbicpKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGV4cGFuc2lvbihtdWx0ID0gYygwLCAwLjA1KSkpICsgc2NhbGVfeF9kaXNjcmV0ZShicmVha3MgPSAnJykgKwogIHRoZW1lX2J3KCkgKyBsYWJzKHggPSAnJywgeSA9ICdUb3RhbFxuRXJyb3IgUmF0ZScsIHN1YnRpdGxlID0gJ1V0b3BpYSBEaXN0YW5jZSArIFByaW9yaXR5JykKCmcudHlwMSA9IGdncGxvdChlcnIuc3ZtLnJhZGFuKSArCiAgZ2VvbV9jb2wobWFwcGluZyA9IGFlcyh4ID0gc291cmNlLCB5ID0gdHlwMSwgZmlsbCA9IHNvdXJjZSkpICsKICBnZW9tX2Vycm9yYmFyKG1hcHBpbmcgPSBhZXMoeCA9IHNvdXJjZSwgeW1pbiA9IHR5cDEgLSB0eXAxLmVyciwgeW1heCA9IHR5cDEgKyB0eXAxLmVyciksIHdpZHRoID0gMC41KSArCiAgZmFjZXRfd3JhcCh+bWV0aG9kLCBucm93ID0gMSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKGxhYmVscyA9IGMoJzBwYXJldCcgPSAnU3RhcnRpbmcgRGF0YXNldCcsICcxYWRhcHQnID0gJysgQWRhcHRpdmUgU2FtcGxpbmcnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJzJyYW5kbycgPSAnKyBSYW5kb20gU2FtcGxpbmcnKSwgbmFtZSA9ICcnLAogICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYygnMHBhcmV0JyA9ICdza3libHVlMicsICcxYWRhcHQnID0gJ3JlZCcsICcycmFuZG8nID0gJ2dyZWVuJykpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gZXhwYW5zaW9uKG11bHQgPSBjKDAsIDAuMDUpKSkgKyBzY2FsZV94X2Rpc2NyZXRlKGJyZWFrcyA9ICcnKSArCiAgdGhlbWVfYncoKSArIGxhYnMoeCA9ICcnLCB5ID0gJ0ZhbHNlIFBvc2l0aXZlXG5FcnJvciBSYXRlJykKCmcudHlwMiA9IGdncGxvdChlcnIuc3ZtLnJhZGFuKSArCiAgZ2VvbV9jb2wobWFwcGluZyA9IGFlcyh4ID0gc291cmNlLCB5ID0gdHlwMiwgZmlsbCA9IHNvdXJjZSkpICsKICBnZW9tX2Vycm9yYmFyKG1hcHBpbmcgPSBhZXMoeCA9IHNvdXJjZSwgeW1pbiA9IHR5cDIgLSB0eXAyLmVyciwgeW1heCA9IHR5cDIgKyB0eXAyLmVyciksIHdpZHRoID0gMC41KSArCiAgZmFjZXRfd3JhcCh+bWV0aG9kLCBucm93ID0gMSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKGxhYmVscyA9IGMoJzBwYXJldCcgPSAnU3RhcnRpbmcgRGF0YXNldCcsICcxYWRhcHQnID0gJysgQWRhcHRpdmUgU2FtcGxpbmcnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJzJyYW5kbycgPSAnKyBSYW5kb20gU2FtcGxpbmcnKSwgbmFtZSA9ICcnLAogICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYygnMHBhcmV0JyA9ICdza3libHVlMicsICcxYWRhcHQnID0gJ3JlZCcsICcycmFuZG8nID0gJ2dyZWVuJykpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gZXhwYW5zaW9uKG11bHQgPSBjKDAsIDAuMDUpKSkgKyBzY2FsZV94X2Rpc2NyZXRlKGJyZWFrcyA9ICcnKSArCiAgdGhlbWVfYncoKSArIGxhYnMoeCA9ICcnLCB5ID0gJ0ZhbHNlIE5lZ2F0aXZlXG5FcnJvciBSYXRlJykKCihnLnJhdGUgKyBndWlkZXMoZmlsbCA9IEZBTFNFKSkgLyBnLnR5cDEgLyAoZy50eXAyICsgZ3VpZGVzKGZpbGwgPSBGQUxTRSkpCndyaXRlLmNzdihlcnIuc3ZtLmN1dG9mLCAnU1ZNX2Vycm9yX3JhZGFuLmNzdicsIHJvdy5uYW1lcyA9IEYpCgpybShlcnIucGFyZXQsIGVyci5hZGFwdCwgZXJyLnJhbmRvLCBnLnJhdGUsIGcudHlwMSwgZy50eXAyKQoKYGBgCgpgYGB7ciBTVk0gTWFyZ2luYWxzOiBVdG9waWEgRGlzdGFuY2V9CnN2bS5tYXJnaW4ucGFyZXQgPSBtYXJnaW5hbChtb2QgPSBtb2QucGFyZXQpOyBzdm0ubWFyZ2luLnBhcmV0JGNhdCA9ICdzdGFydCcKc3ZtLm1hcmdpbi5hZGFwdCA9IG1hcmdpbmFsKG1vZCA9IG1vZC5hZGFwdCk7IHN2bS5tYXJnaW4uYWRhcHQkY2F0ID0gJ2FkYXB0Jwpzdm0ubWFyZ2luLnJhbmRvID0gbWFyZ2luYWwobW9kID0gbW9kLnJhbmRvKTsgc3ZtLm1hcmdpbi5yYW5kbyRjYXQgPSAncmFuZG8nCnN2bS5tYXJnaW4gPSByYmluZChzdm0ubWFyZ2luLnBhcmV0LCBzdm0ubWFyZ2luLmFkYXB0LCBzdm0ubWFyZ2luLnJhbmRvKQpybShzdm0ubWFyZ2luLnBhcmV0LCBzdm0ubWFyZ2luLmFkYXB0LCBzdm0ubWFyZ2luLnJhbmRvKQoKd3JpdGUuY3N2KHN2bS5tYXJnaW4sICdNYXJnaW5fU1ZNX3JhZGFuLmNzdicsIHJvdy5uYW1lcyA9IEYpCgpJbmZlci5wbHQgPSByZWFkLmNzdignLi4vRXhfUXVhcnRpYy9NYXJnaW5hbHNfcmFkYW4uY3N2JykKSW5mZXIucGx0ID0gSW5mZXIucGx0WywhbmFtZXMoSW5mZXIucGx0KSAlaW4lIGMoJ1gnKV0KIyBuYW1lcyhJbmZlci5wbHQpCkluZmVyLnBsdCRtZXRob2QgPSAnR1AnCgpnZ3Bsb3QoKSArCiAgZ2VvbV9wYXRoKGRhdGEgPSBmaWx0ZXIoSW5mZXIucGx0LCBjYXQgPT0gJ3RydScpLCBtYXBwaW5nID0gYWVzKHggPSB4LCB5ID0gcHJvYiAtIHBzZCwgY29sb3IgPSBjYXQpLCBsaW5ldHlwZSA9IDMpICsKICBnZW9tX3BhdGgoZGF0YSA9IGZpbHRlcihJbmZlci5wbHQsIGNhdCA9PSAndHJ1JyksIG1hcHBpbmcgPSBhZXMoeCA9IHgsIHkgPSBwcm9iICsgcHNkLCBjb2xvciA9IGNhdCksIGxpbmV0eXBlID0gMykgKwogIGdlb21fcGF0aChkYXRhID0gc3ZtLm1hcmdpbiwgbWFwcGluZyA9IGFlcyh4ID0geCwgeSA9IHByb2IsIGxpbmV0eXBlID0gbWV0aG9kLCBjb2xvciA9IGNhdCkpICsKICBmYWNldF93cmFwKH52YXIsIG5yb3cgPSAyKSArIHRoZW1lX2J3KCkgKwogIGxhYnMoeCA9ICdJbnB1dCBWYWx1ZScsIHkgPSAnQ29uZGl0aW9uYWwgUHJvYmFiaWxpdHknLCBsaW5ldHlwZSA9ICdTVk0gS2VybmVsJywgY29sb3IgPSAnJywgCiAgICAgICBzdWJ0aXRsZSA9ICdVdG9waWEgRGlzdGFuY2UgKyBQcmlvcml0eScpICsKICBzY2FsZV9jb2xvcl9tYW51YWwobGFiZWxzID0gYygndHJ1JyA9ICdFeHBlY3RlZCBNYXJnaW5hbCcsICdzdGFydCcgPSAnU3RhcnRpbmcgRGF0YXNldCcsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ2FkYXB0JyA9ICcrIEFkYXB0aXZlIFNhbXBsaW5nJywgJ3JhbmRvJyA9ICcrIFJhbmRvbSBTYW1wbGluZycpLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBjKCd0cnUnID0gJ2JsYWNrJywgJ3N0YXJ0JyA9ICdza3libHVlMicsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ2FkYXB0JyA9ICdyZWQnLCAncmFuZG8nID0gJ2dyZWVuJyksCiAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IGMoJ3RydScsICdzdGFydCcsICdhZGFwdCcsICdyYW5kbycpKSArCiAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3QobGluZXR5cGUgPSBjKDMsIDEsIDEsIDEpKSkpICsKICBzY2FsZV9saW5ldHlwZV9kaXNjcmV0ZShsYWJlbHMgPSBjKCdTVk0tcG9sJyA9ICdQb2x5bm9taWFsJywgJ3JhZCcgPSAnU1ZNLVJhZGlhbCcpKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCkpICsgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgMSkpCgpnZ3Bsb3QoKSArCiAgZ2VvbV9wYXRoKGRhdGEgPSBmaWx0ZXIoSW5mZXIucGx0LCBjYXQgPT0gJ3RydScpLCBtYXBwaW5nID0gYWVzKHggPSB4LCB5ID0gcHJvYiAtIHBzZCwgY29sb3IgPSBjYXQpLCBsaW5ldHlwZSA9IDMpICsKICBnZW9tX3BhdGgoZGF0YSA9IGZpbHRlcihJbmZlci5wbHQsIGNhdCA9PSAndHJ1JyksIG1hcHBpbmcgPSBhZXMoeCA9IHgsIHkgPSBwcm9iICsgcHNkLCBjb2xvciA9IGNhdCksIGxpbmV0eXBlID0gMykgKwogIGdlb21fcGF0aChkYXRhID0gZmlsdGVyKEluZmVyLnBsdCwgY2F0ICE9ICd0cnUnKSwgbWFwcGluZyA9IGFlcyh4ID0geCwgeSA9IHByb2IsIGNvbG9yID0gY2F0KSkgKwogIGZhY2V0X3dyYXAodmFyfi4sIG5yb3cgPSAyKSArIAogIHNjYWxlX2NvbG9yX21hbnVhbChsYWJlbHMgPSBjKCd0cnUnID0gJ0V4cGVjdGVkIE1hcmdpbmFsJywgJ3N0YXJ0JyA9ICdTdGFydGluZyBEYXRhc2V0JywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnYWRhcHQnID0gJysgQWRhcHRpdmUgU2FtcGxpbmcnLCAncmFuZG8nID0gJysgUmFuZG9tIFNhbXBsaW5nJyksCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGMoJ3RydScgPSAnYmxhY2snLCAnc3RhcnQnID0gJ3NreWJsdWUyJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnYWRhcHQnID0gJ3JlZCcsICdyYW5kbycgPSAnZ3JlZW4nKSwKICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYygndHJ1JywgJ3N0YXJ0JywgJ2FkYXB0JywgJ3JhbmRvJykpICsKICBndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChsaW5ldHlwZSA9IGMoMywgMSwgMSwgMSkpKSkgKwogIGxhYnMoeCA9ICcnLCB5ID0gJ1Byb2JhYmlsaXR5IG9mIEFjY2VwdGFuY2UnLCBzdWJ0aXRsZSA9ICdDdXRvZmYgVGhyZXNob2xkJywgY29sb3IgPSAnJykgKwogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDAuMDUpKSArIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApKSArCiAgdGhlbWVfYncoKQoKIyBDYWxjdWxhdGUgY29lZmZpY2llbnRzIG9mIGRldGVybWluYXRpb246IGVhc2llciBjb21wYXJpc29uCmNvZWZkZXQgPSBkYXRhLmZyYW1lKG1ldGhvZCA9IGMocmVwKCdTVk0tcG9sJywgMyksIHJlcCgnU1ZNLXJhZCcsIDMpLCByZXAoJ0dQJywgMykpLAogICAgICAgICAgIGRhdGFzZXQgPSBjKCcwc3RhcnQnLCAnMWFkYXB0JywgJzJyYW5kbycpLAogY29lZiA9IGMoY29yKHggPSBmaWx0ZXIoc3ZtLm1hcmdpbiwgbWV0aG9kID09ICdTVk0tcG9sJywgY2F0ID09ICdzdGFydCcpJHByb2IsIAogICAgeSA9IGZpbHRlcihJbmZlci5wbHQsIGNhdCA9PSAndHJ1JykkcHJvYiwgbWV0aG9kID0gJ3BlYXJzb24nKV4yLAogIGNvcih4ID0gZmlsdGVyKHN2bS5tYXJnaW4sIG1ldGhvZCA9PSAnU1ZNLXBvbCcsIGNhdCA9PSAnYWRhcHQnKSRwcm9iLCAKICAgIHkgPSBmaWx0ZXIoSW5mZXIucGx0LCBjYXQgPT0gJ3RydScpJHByb2IsIG1ldGhvZCA9ICdwZWFyc29uJyleMiwKICBjb3IoeCA9IGZpbHRlcihzdm0ubWFyZ2luLCBtZXRob2QgPT0gJ1NWTS1wb2wnLCBjYXQgPT0gJ3JhbmRvJykkcHJvYiwgCiAgICB5ID0gZmlsdGVyKEluZmVyLnBsdCwgY2F0ID09ICd0cnUnKSRwcm9iLCBtZXRob2QgPSAncGVhcnNvbicpXjIsCiAgY29yKHggPSBmaWx0ZXIoc3ZtLm1hcmdpbiwgbWV0aG9kID09ICdTVk0tcmFkJywgY2F0ID09ICdzdGFydCcpJHByb2IsIAogICAgeSA9IGZpbHRlcihJbmZlci5wbHQsIGNhdCA9PSAndHJ1JykkcHJvYiwgbWV0aG9kID0gJ3BlYXJzb24nKV4yLAogIGNvcih4ID0gZmlsdGVyKHN2bS5tYXJnaW4sIG1ldGhvZCA9PSAnU1ZNLXJhZCcsIGNhdCA9PSAnYWRhcHQnKSRwcm9iLCAKICAgIHkgPSBmaWx0ZXIoSW5mZXIucGx0LCBjYXQgPT0gJ3RydScpJHByb2IsIG1ldGhvZCA9ICdwZWFyc29uJyleMiwKICBjb3IoeCA9IGZpbHRlcihzdm0ubWFyZ2luLCBtZXRob2QgPT0gJ1NWTS1yYWQnLCBjYXQgPT0gJ3JhbmRvJykkcHJvYiwgCiAgICB5ID0gZmlsdGVyKEluZmVyLnBsdCwgY2F0ID09ICd0cnUnKSRwcm9iLCBtZXRob2QgPSAncGVhcnNvbicpXjIsCiAgY29yKHggPSBmaWx0ZXIoSW5mZXIucGx0LCBjYXQgPT0gJ3N0YXJ0JykkcHJvYiwgCiAgICB5ID0gYyhmaWx0ZXIoSW5mZXIucGx0LCBjYXQgPT0gJ3RydScsIHZhciA9PSAneDEnKSRwcm9iLCBmaWx0ZXIoSW5mZXIucGx0LCBjYXQgPT0gJ3RydScsIHZhciA9PSAneDInKSRwcm9iKSwgCiAgICBtZXRob2QgPSAncGVhcnNvbicpXjIsCiAgY29yKHggPSBmaWx0ZXIoSW5mZXIucGx0LCBjYXQgPT0gJ2FkYXB0JykkcHJvYiwgCiAgICB5ID0gYyhmaWx0ZXIoSW5mZXIucGx0LCBjYXQgPT0gJ3RydScsIHZhciA9PSAneDEnKSRwcm9iLCBmaWx0ZXIoSW5mZXIucGx0LCBjYXQgPT0gJ3RydScsIHZhciA9PSAneDInKSRwcm9iKSwgCiAgICBtZXRob2QgPSAncGVhcnNvbicpXjIsCiAgY29yKHggPSBmaWx0ZXIoSW5mZXIucGx0LCBjYXQgPT0gJ3JhbmRvJykkcHJvYiwgCiAgICB5ID0gYyhmaWx0ZXIoSW5mZXIucGx0LCBjYXQgPT0gJ3RydScsIHZhciA9PSAneDEnKSRwcm9iLCBmaWx0ZXIoSW5mZXIucGx0LCBjYXQgPT0gJ3RydScsIHZhciA9PSAneDInKSRwcm9iKSwgCiAgICBtZXRob2QgPSAncGVhcnNvbicpXjIpKQoKY29lZmRldAp3cml0ZS5jc3YoY29lZmRldCwgJ01hcmdpbmFsc19Db2VmRGV0X3JhZGFuLmNzdicsIHJvdy5uYW1lcyA9IEYpCgpnZ3Bsb3QoY29lZmRldCkgKwogIGdlb21fY29sKG1hcHBpbmcgPSBhZXMoeCA9IGRhdGFzZXQsIGZpbGwgPSBkYXRhc2V0LCB5ID0gY29lZikpKwogIGZhY2V0X2dyaWQofm1ldGhvZCkgKwogIGxhYnMoeCA9ICcnLCB5ID0gJzEtVmFyaWFibGUgTWFyZ2luYWwgQ29lZmZpY2llbnQgb2YgRGV0ZXJtaW5hdGlvbicsIHN1YnRpdGxlID0gJ1V0b3BpYSBEaXN0YW5jZSArIFByaW9yaXR5JykgKwogIHNjYWxlX3hfZGlzY3JldGUoYnJlYWtzID0gYygpKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobGFiZWxzID0gYygnMHN0YXJ0JyA9ICdTdGFydGluZyBEYXRhc2V0JywgJzFhZGFwdCcgPSAnKyBBZGFwdGl2ZSBTYW1wbGluZycsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnMnJhbmRvJyA9ICcrIFJhbmRvbSBTYW1wbGluZycpLCBuYW1lID0gJycsCiAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYygnMHN0YXJ0JyA9ICdza3libHVlMicsICcxYWRhcHQnID0gJ3JlZCcsICcycmFuZG8nID0gJ2dyZWVuJykpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gZXhwYW5zaW9uKG11bHQgPSBjKDAsIDAuMDUpKSkKCmBgYAoKQWx0ZXJuYXRpdmUgTWV0cmljOiBBY2N1cmFjeSBhcyBuIHNhbXBsZXMgYXJlIGNvbGxlY3RlZAoKYGBge3J9ClNWTS5lcnIubiA9IGZ1bmN0aW9uKGZpbmUuaW5wdXQsIGlucHV0LmRhdGEpewogIG1vZC5saXN0ID0gU1ZNLm1vZC5hbGwoaW5wdXQuZGF0YSA9IGlucHV0LmRhdGEpCiAgZXJyID0gU1ZNLmVycihmaW5lLmlucHV0ID0gZmluZS5pbnB1dCwgbW9kLmxpc3QgPSBtb2QubGlzdCkKICAjIHJldHVybihlcnIpCiAgIyBBZGRpdGlvbmFsIHByb2Nlc3NpbmcgdG8gbWFrZSBpdCBjb25zaXN0ZW50IHdpdGggdGhlIG90aGVyIGNvbnZlbnRpb24KICBlcnIubiA9IGRhdGEuZnJhbWUoCiAgICBtZXRob2QgPSBlcnIkbWV0aG9kLAogICAgZXJyID0gYyhlcnIkcmF0ZSwgZXJyJHR5cDEsICAgICBlcnIkdHlwMiksCiAgICAjIHVuYyA9IGMoZXJyJGVyciwgIGVyciR0eXAxLmVyciwgZXJyJHR5cDIuZXJyKSwKICAgIHR5cCA9IGMocmVwKCdUb3RhbCcsIG5yb3coZXJyKSksIHJlcCgnRmFsc2UgUG9zaXRpdmUnLCBucm93KGVycikpLCAKICAgICAgICAgICAgcmVwKCdGYWxzZSBOZWdhdGl2ZScsIG5yb3coZXJyKSkpCiAgKQogICMgUGxhY2UgbGltaXRlciBvbiB1bmNlcnRhaW50eQogICMgZXJyLm4kdW5jW2Vyci5uJHVuYyA+IGVyci5uJGVycl0gPSBlcnIubiRlcnJbZXJyLm4kdW5jID4gZXJyLm4kZXJyXQogICMgTG93ZXIgYm91bmQgb2YgMWUtOCB0byBhdm9pZCBOYU4gdmFsdWVzIGxhdGVyCiAgIyBlcnIubiR1bmNbZXJyLm4kdW5jIDwgMWUtOF0gPSAxZS04CiAgZXJyLm4kZXJyW2Vyci5uJGVyciA8IDFlLThdID0gMWUtOAogIHJldHVybihlcnIubikKfQoKYGBgCgpgYGB7ciBTVk06IE4gU2FtcGxlIEVmZmVjdCAtIFBhcmV0byBkaXN0YW5jZX0KIyBQYXJldG8gRGlzdGFuY2UKZGF0YS5wYXJldCA9IHJlYWQuY3N2KCcuLi9FeF9RdWFydGljL0dQYXJfYWxsX3N0YXJ0LmNzdicpCmRhdGEuZGVsdGEgPSByZWFkLmNzdignLi4vRXhfUXVhcnRpYy9HUGFyX0FjY2VwdF9EZWx0YTEuY3N2JykKZGF0YS5yYW5kbyA9IHJlYWQuY3N2KGZpbGUgPSAnR1Bhcl9SYW5kb20uY3N2JykKCiMgRGVmaW5lIGFjY2VwdGFuY2UKZGF0YS5wYXJldCRjYXQgPSAwOyBkYXRhLmRlbHRhJGNhdCA9IDA7IGRhdGEucmFuZG8kY2F0ID0gMDsgZmluZS5ncmlkJGNhdCA9IDAKZGF0YS5wYXJldCRjYXRbZGF0YS5wYXJldCRkaXN0IDw9IDFdID0gMQpkYXRhLmRlbHRhJGNhdFtkYXRhLmRlbHRhJGRpc3QgPD0gMV0gPSAxCmRhdGEucmFuZG8kY2F0W2RhdGEucmFuZG8kZGlzdCA8PSAxXSA9IDEKZmluZS5ncmlkJGNhdFtmaW5lLmdyaWQkZGlzdCA8PSAxXSA9IDEKCiMgTG9vcApuc2FtcCA9IHNlcShmcm9tID0gbnJvdyhkYXRhLnBhcmV0KSsxLCB0byA9IG5yb3coZGF0YS5kZWx0YSksIGJ5ID0gMSkKCiMgWmVybyBzYW1wbGUgc3RhdGUKZXJyMCA9IFNWTS5lcnIubihmaW5lLmlucHV0ID0gZmluZS5ncmlkLCBpbnB1dC5kYXRhID0gZGF0YS5wYXJldCkKCiMgRWZmZWN0IG9mIHNhbXBsaW5nCm4uY29yZXMgPC0gcGFyYWxsZWw6OmRldGVjdENvcmVzKCkgLSAxCm15LmNsdXN0ZXIgPC0gcGFyYWxsZWw6Om1ha2VDbHVzdGVyKAogIG4uY29yZXMsCiAgdHlwZSA9ICJQU09DSyIKICApCmRvUGFyYWxsZWw6OnJlZ2lzdGVyRG9QYXJhbGxlbChjbCA9IG15LmNsdXN0ZXIpCmZvcmVhY2g6OmdldERvUGFyUmVnaXN0ZXJlZCgpCgplcnIubiA9IGZvcmVhY2goaSA9IG5zYW1wLCAuY29tYmluZSA9ICdyYmluZCcpICVkb3BhciUgewogICMgQWRhcHRpdmUKICByZXMgPSBTVk0uZXJyLm4oZmluZS5pbnB1dCA9IGZpbmUuZ3JpZCwgaW5wdXQuZGF0YSA9IGRhdGEuZGVsdGFbMTppLF0pCiAgIyBMYWJlbHMKICByZXMkbiA9IGktbnJvdyhkYXRhLnBhcmV0KQogIHJlcyRjbGFzcyA9ICdQYXJldG8gRGlzdGFuY2UnCiAgcmVzJHNhbXAgPSAnQWRhcHRpdmUnCiAgIyBOb3JtYWxpemVkIGVycm9ycwogIHJlcyRlcnIubiA9IHJlcyRlcnIvZXJyMCRlcnIKICByZXMkdW5jLm1pbiA9IHJlcyRlcnI7ICAgICByZXMkdW5jLm1heCA9IHJlcyRlcnIKICByZXMkdW5jLm1pbi5uID0gcmVzJGVyci5uOyByZXMkdW5jLm1heC5uID0gcmVzJGVyci5uCiAgZXJyLmFkYXB0ID0gcmVzCiAgIyByZXMkdW5jLm4gPSBzcXJ0KHJlcyRlcnIubl4yICogKChyZXMkdW5jL3JlcyRlcnIpXjIgKyAoZXJyMCR1bmMvZXJyMCRlcnIpXjIpKQogICMgZXJyLm4gPSByYmluZChlcnIubiwgcmVzKQogIAogICMgUmFuZG9tIHNhbXBsZXMKICBuc2V0ID0gaSAtIG5yb3coZGF0YS5wYXJldCkKICByZXMucmFuZG8gPSBkYXRhLmZyYW1lKCkKICBmb3IoaiBpbiAxOjMwKXsgCiAgICAjIENvbGxlY3QgbXVsdGlwbGUgcmFuZG9tIGRhdGFzZXRzIHRvIGdldCBhIHNlbnNlIG9mIHRoZSB2YXJpYW5jZQogICAgcmFuZG8gPSBkYXRhLnJhbmRvW2MoMTpucm93KGRhdGEucGFyZXQpLCAKICAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZSh4ID0gKG5yb3coZGF0YS5wYXJldCkrMSk6bnJvdyhkYXRhLnJhbmRvKSwgc2l6ZSA9IG5zZXQpKSxdCiAgICByZXMgPSBTVk0uZXJyLm4oZmluZS5pbnB1dCA9IGZpbmUuZ3JpZCwgaW5wdXQuZGF0YSA9IHJhbmRvKQogICAgIyBOb3JtYWxpemVkIHRvIHRoZSBzdGFydGluZyBzdGF0ZQogICAgcmVzJGVyci5uID0gcmVzJGVyci9lcnIwJGVycgogICAgcmVzLnJhbmRvID0gcmJpbmQocmVzLnJhbmRvLCByZXMpCiAgfQogIGVyci5yYW5kbyA9IGRhdGEuZnJhbWUoKQogICMgVW5jZXJ0YWludHkgY29ycmVjdGlvbiBiYXNlZCBvbiB0aGUgdmFyaWFuY2UgLSBpbiB0aGlzIGNhc2Ugd2FudCB0aGUgOTUlIENJCiAgZm9yKG1ldCBpbiB1bmlxdWUocmVzLnJhbmRvJG1ldGhvZCkpewogICAgZm9yKHRwIGluIHVuaXF1ZShyZXMucmFuZG8kdHlwKSl7CiAgICAgIHN1YiA9IGRwbHlyOjpmaWx0ZXIocmVzLnJhbmRvLCBtZXRob2QgPT0gbWV0LCB0eXAgPT0gdHApCiAgICAgIGVyci5yYW5kbyA9IHJiaW5kKGVyci5yYW5kbywgZGF0YS5mcmFtZSgKICAgICAgICAgIGVyciA9IG1lZGlhbihzdWIkZXJyKSwgIGVyci5uID0gbWVkaWFuKHN1YiRlcnIubiksCiAgICAgICAgICB1bmMubWluID0gcXVhbnRpbGUoeCA9IHN1YiRlcnIsIHByb2JzID0gMC4wMjUpLCAKICAgICAgICAgIHVuYy5tYXggPSBxdWFudGlsZSh4ID0gc3ViJGVyciwgcHJvYnMgPSAwLjk3NSksIAogICAgICAgICAgdW5jLm1pbi5uID0gcXVhbnRpbGUoeCA9IHN1YiRlcnIubiwgcHJvYnMgPSAwLjAyNSksIAogICAgICAgICAgdW5jLm1heC5uID0gcXVhbnRpbGUoeCA9IHN1YiRlcnIubiwgcHJvYnMgPSAwLjk3NSksIAogICAgICAgICAgY2xhc3MgPSAnUGFyZXRvIERpc3RhbmNlJywgdHlwID0gdHAsIG1ldGhvZCA9IG1ldCwKICAgICAgICAgIG4gPSBpIC0gbnJvdyhkYXRhLnBhcmV0KSwKICAgICAgICAgIHNhbXAgPSAnUmFuZG9tJykpCiAgICB9CiAgfQogIHJiaW5kKGVyci5hZGFwdCwgZXJyLnJhbmRvKQp9CnBhcmFsbGVsOjpzdG9wQ2x1c3RlcihjbCA9IG15LmNsdXN0ZXIpCgojIGVyci5uCiMgQ29tYmluZSB3aXRoIDAgc3RhdGUKZXJyMCRuID0gMAplcnIwJGNsYXNzID0gJ1BhcmV0byBEaXN0YW5jZScKZXJyMCRzYW1wID0gJ0FkYXB0aXZlJwplcnIwJGVyci5uID0gMQplcnIwJHVuYy5tYXggICA9IGVycjAkZXJyOyBlcnIwJHVuYy5taW4gICA9IGVycjAkZXJyCmVycjAkdW5jLm1heC5uID0gMTsgICAgICAgIGVycjAkdW5jLm1pbi5uID0gMQplcnIubiA9IHJiaW5kKGVycjAsIGVyci5uKQplcnIwJHNhbXAgPSAnUmFuZG9tJwplcnIubiA9IHJiaW5kKGVycjAsIGVyci5uKQoKIyBQbG90dGluZwojIGVyci5uCmdncGxvdChlcnIubikgKwogIGdlb21fZXJyb3JiYXIobWFwcGluZyA9IGFlcyh4ID0gbiwgeSA9IGVyci5uLCB5bWluID0gdW5jLm1pbi5uLCB5bWF4ID0gdW5jLm1heC5uLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IHNhbXApKSArCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBuLCB5ID0gZXJyLm4sIGNvbG9yID0gc2FtcCkpICsKICBmYWNldF9ncmlkKHR5cH5tZXRob2QsIHNjYWxlID0gJ2ZyZWUnKQpnZ3Bsb3QoZXJyLm4pICsKICBnZW9tX2Vycm9yYmFyKG1hcHBpbmcgPSBhZXMoeCA9IG4sIHkgPSBlcnIsIHltaW4gPSB1bmMubWluLCB5bWF4ID0gdW5jLm1heCwgY29sb3IgPSBzYW1wKSkgKwogIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4ID0gbiwgeSA9IGVyciwgY29sb3IgPSBzYW1wKSkgKwogIGZhY2V0X2dyaWQodHlwfm1ldGhvZCwgc2NhbGUgPSAnZnJlZScpCgoKIyBTdG9yZSBkYXRhCmVyci5zdm0gPSBlcnIubgoKYGBgCgpgYGB7ciBTVk06IE4gU2FtcGxlIEVmZmVjdCAtIFRocmVzaG9sZCBDdXRvZmZ9CmRhdGEucGFyZXQgPSByZWFkLmNzdignLi4vRXhfUXVhcnRpYy9HUGFyX2FsbF9zdGFydC5jc3YnKQpkYXRhLmN1dG9mID0gcmVhZC5jc3YoJy4uL0V4X1F1YXJ0aWMvR1Bhcl9BY2NlcHRfVGhyZXNob2xkLmNzdicpCmRhdGEucmFuZG8gPSByZWFkLmNzdihmaWxlID0gJ0dQYXJfUmFuZG9tLmNzdicpCgojIERlZmluZSBhY2NlcHRhbmNlCmRhdGEucGFyZXQkY2F0ID0gMDsgZGF0YS5jdXRvZiRjYXQgPSAwOyBkYXRhLnJhbmRvJGNhdCA9IDA7IGZpbmUuZ3JpZCRjYXQgPSAwCmRhdGEucGFyZXQkY2F0W2RhdGEucGFyZXQkZjEubm9ybSA8PSAxICYgZGF0YS5wYXJldCRmMi5ub3JtIDw9IDFdID0gMQpkYXRhLmN1dG9mJGNhdFtkYXRhLmN1dG9mJGYxLm5vcm0gPD0gMSAmIGRhdGEuY3V0b2YkZjIubm9ybSA8PSAxXSA9IDEKZGF0YS5yYW5kbyRjYXRbZGF0YS5yYW5kbyRmMS5ub3JtIDw9IDEgJiBkYXRhLnJhbmRvJGYyLm5vcm0gPD0gMV0gPSAxCmZpbmUuZ3JpZCRjYXRbZmluZS5ncmlkJGYxLm5vcm0gPD0gMSAmIGZpbmUuZ3JpZCRmMi5ub3JtIDw9IDFdID0gMQoKIyBMb29wCm5zYW1wID0gc2VxKGZyb20gPSBucm93KGRhdGEucGFyZXQpKzEsIHRvID0gbnJvdyhkYXRhLmN1dG9mKSwgYnkgPSAxKQoKIyBaZXJvIHNhbXBsZSBzdGF0ZQplcnIwID0gU1ZNLmVyci5uKGZpbmUuaW5wdXQgPSBmaW5lLmdyaWQsIGlucHV0LmRhdGEgPSBkYXRhLnBhcmV0KQoKIyBFZmZlY3Qgb2Ygc2FtcGxpbmcKbi5jb3JlcyA8LSBwYXJhbGxlbDo6ZGV0ZWN0Q29yZXMoKSAtIDEKbXkuY2x1c3RlciA8LSBwYXJhbGxlbDo6bWFrZUNsdXN0ZXIoCiAgbi5jb3JlcywKICB0eXBlID0gIlBTT0NLIgogICkKZG9QYXJhbGxlbDo6cmVnaXN0ZXJEb1BhcmFsbGVsKGNsID0gbXkuY2x1c3RlcikKZm9yZWFjaDo6Z2V0RG9QYXJSZWdpc3RlcmVkKCkKCmVyci5uID0gZm9yZWFjaChpID0gbnNhbXAsIC5jb21iaW5lID0gJ3JiaW5kJykgJWRvcGFyJSB7CiAgIyBBZGFwdGl2ZQogIHJlcyA9IFNWTS5lcnIubihmaW5lLmlucHV0ID0gZmluZS5ncmlkLCBpbnB1dC5kYXRhID0gZGF0YS5jdXRvZlsxOmksXSkKICAjIExhYmVscwogIHJlcyRuID0gaS1ucm93KGRhdGEucGFyZXQpCiAgcmVzJGNsYXNzID0gJ1RocmVzaG9sZCBDdXRvZmYnCiAgcmVzJHNhbXAgPSAnQWRhcHRpdmUnCiAgIyBOb3JtYWxpemVkIGVycm9ycwogIHJlcyRlcnIubiA9IHJlcyRlcnIvZXJyMCRlcnIKICByZXMkdW5jLm1pbiA9IHJlcyRlcnI7ICAgICByZXMkdW5jLm1heCA9IHJlcyRlcnIKICByZXMkdW5jLm1pbi5uID0gcmVzJGVyci5uOyByZXMkdW5jLm1heC5uID0gcmVzJGVyci5uCiAgZXJyLmFkYXB0ID0gcmVzCiAgIyByZXMkdW5jLm4gPSBzcXJ0KHJlcyRlcnIubl4yICogKChyZXMkdW5jL3JlcyRlcnIpXjIgKyAoZXJyMCR1bmMvZXJyMCRlcnIpXjIpKQogICMgZXJyLm4gPSByYmluZChlcnIubiwgcmVzKQogIAogICMgUmFuZG9tIHNhbXBsZXMKICBuc2V0ID0gaSAtIG5yb3coZGF0YS5wYXJldCkKICByZXMucmFuZG8gPSBkYXRhLmZyYW1lKCkKICBmb3IoaiBpbiAxOjMwKXsgCiAgICAjIENvbGxlY3QgbXVsdGlwbGUgcmFuZG9tIGRhdGFzZXRzIHRvIGdldCBhIHNlbnNlIG9mIHRoZSB2YXJpYW5jZQogICAgcmFuZG8gPSBkYXRhLnJhbmRvW2MoMTpucm93KGRhdGEucGFyZXQpLCAKICAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZSh4ID0gKG5yb3coZGF0YS5wYXJldCkrMSk6bnJvdyhkYXRhLnJhbmRvKSwgc2l6ZSA9IG5zZXQpKSxdCiAgICByZXMgPSBTVk0uZXJyLm4oZmluZS5pbnB1dCA9IGZpbmUuZ3JpZCwgaW5wdXQuZGF0YSA9IHJhbmRvKQogICAgIyBOb3JtYWxpemVkIHRvIHRoZSBzdGFydGluZyBzdGF0ZQogICAgcmVzJGVyci5uID0gcmVzJGVyci9lcnIwJGVycgogICAgcmVzLnJhbmRvID0gcmJpbmQocmVzLnJhbmRvLCByZXMpCiAgfQogIGVyci5yYW5kbyA9IGRhdGEuZnJhbWUoKQogICMgVW5jZXJ0YWludHkgY29ycmVjdGlvbiBiYXNlZCBvbiB0aGUgdmFyaWFuY2UgLSBpbiB0aGlzIGNhc2Ugd2FudCB0aGUgOTUlIENJCiAgZm9yKG1ldCBpbiB1bmlxdWUocmVzLnJhbmRvJG1ldGhvZCkpewogICAgZm9yKHRwIGluIHVuaXF1ZShyZXMucmFuZG8kdHlwKSl7CiAgICAgIHN1YiA9IGRwbHlyOjpmaWx0ZXIocmVzLnJhbmRvLCBtZXRob2QgPT0gbWV0LCB0eXAgPT0gdHApCiAgICAgIGVyci5yYW5kbyA9IHJiaW5kKGVyci5yYW5kbywgZGF0YS5mcmFtZSgKICAgICAgICAgIGVyciA9IG1lZGlhbihzdWIkZXJyKSwgIGVyci5uID0gbWVkaWFuKHN1YiRlcnIubiksCiAgICAgICAgICB1bmMubWluID0gcXVhbnRpbGUoeCA9IHN1YiRlcnIsIHByb2JzID0gMC4wMjUpLCAKICAgICAgICAgIHVuYy5tYXggPSBxdWFudGlsZSh4ID0gc3ViJGVyciwgcHJvYnMgPSAwLjk3NSksIAogICAgICAgICAgdW5jLm1pbi5uID0gcXVhbnRpbGUoeCA9IHN1YiRlcnIubiwgcHJvYnMgPSAwLjAyNSksIAogICAgICAgICAgdW5jLm1heC5uID0gcXVhbnRpbGUoeCA9IHN1YiRlcnIubiwgcHJvYnMgPSAwLjk3NSksIAogICAgICAgICAgY2xhc3MgPSAnVGhyZXNob2xkIEN1dG9mZicsIHR5cCA9IHRwLCBtZXRob2QgPSBtZXQsCiAgICAgICAgICBuID0gaSAtIG5yb3coZGF0YS5wYXJldCksCiAgICAgICAgICBzYW1wID0gJ1JhbmRvbScpKQogICAgfQogIH0KICByYmluZChlcnIuYWRhcHQsIGVyci5yYW5kbykKfQpwYXJhbGxlbDo6c3RvcENsdXN0ZXIoY2wgPSBteS5jbHVzdGVyKQoKIyBlcnIubgojIENvbWJpbmUgd2l0aCAwIHN0YXRlCmVycjAkbiA9IDAKZXJyMCRjbGFzcyA9ICdUaHJlc2hvbGQgQ3V0b2ZmJwplcnIwJHNhbXAgPSAnQWRhcHRpdmUnCmVycjAkZXJyLm4gPSAxCmVycjAkdW5jLm1heCAgID0gZXJyMCRlcnI7IGVycjAkdW5jLm1pbiAgID0gZXJyMCRlcnIKZXJyMCR1bmMubWF4Lm4gPSAxOyAgICAgICAgZXJyMCR1bmMubWluLm4gPSAxCmVyci5uID0gcmJpbmQoZXJyMCwgZXJyLm4pCmVycjAkc2FtcCA9ICdSYW5kb20nCmVyci5uID0gcmJpbmQoZXJyMCwgZXJyLm4pCgojIFBsb3R0aW5nCiMgZXJyLm4KZ2dwbG90KGVyci5uKSArCiAgZ2VvbV9lcnJvcmJhcihtYXBwaW5nID0gYWVzKHggPSBuLCB5ID0gZXJyLm4sIHltaW4gPSB1bmMubWluLm4sIHltYXggPSB1bmMubWF4Lm4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gc2FtcCkpICsKICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeCA9IG4sIHkgPSBlcnIubiwgY29sb3IgPSBzYW1wKSkgKwogIGZhY2V0X2dyaWQodHlwfm1ldGhvZCwgc2NhbGUgPSAnZnJlZScpCmdncGxvdChlcnIubikgKwogIGdlb21fZXJyb3JiYXIobWFwcGluZyA9IGFlcyh4ID0gbiwgeSA9IGVyciwgeW1pbiA9IHVuYy5taW4sIHltYXggPSB1bmMubWF4LCBjb2xvciA9IHNhbXApKSArCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBuLCB5ID0gZXJyLCBjb2xvciA9IHNhbXApKSArCiAgZmFjZXRfZ3JpZCh0eXB+bWV0aG9kLCBzY2FsZSA9ICdmcmVlJykKCiMgU3RvcmUgZGF0YQplcnIuc3ZtID0gcmJpbmQoZXJyLnN2bSwgZXJyLm4pCgpgYGAKCmBgYHtyIFNWTTogTiBTYW1wbGUgRWZmZWN0IC0gVXRvcGlhIERpc3RhbmNlfQpkYXRhLnBhcmV0ID0gcmVhZC5jc3YoZmlsZSA9ICcuLi9FeF9RdWFydGljL0dQYXJfYWxsX3N0YXJ0LmNzdicpCmRhdGEucmFkYW4gPSByZWFkLmNzdignLi4vRXhfUXVhcnRpYy9HUGFyX0FjY2VwdF9SYWRpdXMuY3N2JykKZGF0YS5yYW5kbyA9IHJlYWQuY3N2KGZpbGUgPSAnR1Bhcl9SYW5kb20uY3N2JykKCiMgRGVmaW5lIGFjY2VwdGFuY2UKZGF0YS5wYXJldCRjYXQgPSAwOyBkYXRhLnJhZGFuJGNhdCA9IDA7IGRhdGEucmFuZG8kY2F0ID0gMDsgZmluZS5ncmlkJGNhdCA9IDAKZGF0YS5wYXJldCRjYXRbc3FydChkYXRhLnBhcmV0JGYxLm5vcm1eMiArIGRhdGEucGFyZXQkZjIubm9ybV4yKSA8PSAxICYgZGF0YS5wYXJldCR0aGV0YSA+IDIwXSA9IDEKZGF0YS5yYWRhbiRjYXRbc3FydChkYXRhLnJhZGFuJGYxLm5vcm1eMiArIGRhdGEucmFkYW4kZjIubm9ybV4yKSA8PSAxICYgZGF0YS5yYWRhbiR0aGV0YSA+IDIwXSA9IDEKZGF0YS5yYW5kbyRjYXRbc3FydChkYXRhLnJhbmRvJGYxLm5vcm1eMiArIGRhdGEucmFuZG8kZjIubm9ybV4yKSA8PSAxICYgZGF0YS5yYW5kbyR0aGV0YSA+IDIwXSA9IDEKZmluZS5ncmlkJGNhdFtzcXJ0KGZpbmUuZ3JpZCRmMS5ub3JtXjIgKyBmaW5lLmdyaWQkZjIubm9ybV4yKSA8PSAxICYgZmluZS5ncmlkJGFuZyA+IDIwXSA9IDEKCiMgTG9vcApuc2FtcCA9IHNlcShmcm9tID0gbnJvdyhkYXRhLnBhcmV0KSsxLCB0byA9IG5yb3coZGF0YS5yYWRhbiksIGJ5ID0gMSkKCiMgWmVybyBzYW1wbGUgc3RhdGUKZXJyMCA9IFNWTS5lcnIubihmaW5lLmlucHV0ID0gZmluZS5ncmlkLCBpbnB1dC5kYXRhID0gZGF0YS5wYXJldCkKCiMgRWZmZWN0IG9mIHNhbXBsaW5nCm4uY29yZXMgPC0gcGFyYWxsZWw6OmRldGVjdENvcmVzKCkgLSAxCm15LmNsdXN0ZXIgPC0gcGFyYWxsZWw6Om1ha2VDbHVzdGVyKAogIG4uY29yZXMsCiAgdHlwZSA9ICJQU09DSyIKICApCmRvUGFyYWxsZWw6OnJlZ2lzdGVyRG9QYXJhbGxlbChjbCA9IG15LmNsdXN0ZXIpCmZvcmVhY2g6OmdldERvUGFyUmVnaXN0ZXJlZCgpCgplcnIubiA9IGZvcmVhY2goaSA9IG5zYW1wLCAuY29tYmluZSA9ICdyYmluZCcpICVkb3BhciUgewogICMgQWRhcHRpdmUKICByZXMgPSBTVk0uZXJyLm4oZmluZS5pbnB1dCA9IGZpbmUuZ3JpZCwgaW5wdXQuZGF0YSA9IGRhdGEucmFkYW5bMTppLF0pCiAgIyBMYWJlbHMKICByZXMkbiA9IGktbnJvdyhkYXRhLnBhcmV0KQogIHJlcyRjbGFzcyA9ICdVdG9waWEgRGlzdGFuY2UnCiAgcmVzJHNhbXAgPSAnQWRhcHRpdmUnCiAgIyBOb3JtYWxpemVkIGVycm9ycwogIHJlcyRlcnIubiA9IHJlcyRlcnIvZXJyMCRlcnIKICByZXMkdW5jLm1pbiA9IHJlcyRlcnI7ICAgICByZXMkdW5jLm1heCA9IHJlcyRlcnIKICByZXMkdW5jLm1pbi5uID0gcmVzJGVyci5uOyByZXMkdW5jLm1heC5uID0gcmVzJGVyci5uCiAgZXJyLmFkYXB0ID0gcmVzCiAgIyByZXMkdW5jLm4gPSBzcXJ0KHJlcyRlcnIubl4yICogKChyZXMkdW5jL3JlcyRlcnIpXjIgKyAoZXJyMCR1bmMvZXJyMCRlcnIpXjIpKQogICMgZXJyLm4gPSByYmluZChlcnIubiwgcmVzKQogIAogICMgUmFuZG9tIHNhbXBsZXMKICBuc2V0ID0gaSAtIG5yb3coZGF0YS5wYXJldCkKICByZXMucmFuZG8gPSBkYXRhLmZyYW1lKCkKICBmb3IoaiBpbiAxOjMwKXsgCiAgICAjIENvbGxlY3QgbXVsdGlwbGUgcmFuZG9tIGRhdGFzZXRzIHRvIGdldCBhIHNlbnNlIG9mIHRoZSB2YXJpYW5jZQogICAgcmFuZG8gPSBkYXRhLnJhbmRvW2MoMTpucm93KGRhdGEucGFyZXQpLCAKICAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZSh4ID0gKG5yb3coZGF0YS5wYXJldCkrMSk6bnJvdyhkYXRhLnJhbmRvKSwgc2l6ZSA9IG5zZXQpKSxdCiAgICByZXMgPSBTVk0uZXJyLm4oZmluZS5pbnB1dCA9IGZpbmUuZ3JpZCwgaW5wdXQuZGF0YSA9IHJhbmRvKQogICAgIyBOb3JtYWxpemVkIHRvIHRoZSBzdGFydGluZyBzdGF0ZQogICAgcmVzJGVyci5uID0gcmVzJGVyci9lcnIwJGVycgogICAgcmVzLnJhbmRvID0gcmJpbmQocmVzLnJhbmRvLCByZXMpCiAgfQogIGVyci5yYW5kbyA9IGRhdGEuZnJhbWUoKQogICMgVW5jZXJ0YWludHkgY29ycmVjdGlvbiBiYXNlZCBvbiB0aGUgdmFyaWFuY2UgLSBpbiB0aGlzIGNhc2Ugd2FudCB0aGUgOTUlIENJCiAgZm9yKG1ldCBpbiB1bmlxdWUocmVzLnJhbmRvJG1ldGhvZCkpewogICAgZm9yKHRwIGluIHVuaXF1ZShyZXMucmFuZG8kdHlwKSl7CiAgICAgIHN1YiA9IGRwbHlyOjpmaWx0ZXIocmVzLnJhbmRvLCBtZXRob2QgPT0gbWV0LCB0eXAgPT0gdHApCiAgICAgIGVyci5yYW5kbyA9IHJiaW5kKGVyci5yYW5kbywgZGF0YS5mcmFtZSgKICAgICAgICAgIGVyciA9IG1lZGlhbihzdWIkZXJyKSwgIGVyci5uID0gbWVkaWFuKHN1YiRlcnIubiksCiAgICAgICAgICB1bmMubWluID0gcXVhbnRpbGUoeCA9IHN1YiRlcnIsIHByb2JzID0gMC4wMjUpLCAKICAgICAgICAgIHVuYy5tYXggPSBxdWFudGlsZSh4ID0gc3ViJGVyciwgcHJvYnMgPSAwLjk3NSksIAogICAgICAgICAgdW5jLm1pbi5uID0gcXVhbnRpbGUoeCA9IHN1YiRlcnIubiwgcHJvYnMgPSAwLjAyNSksIAogICAgICAgICAgdW5jLm1heC5uID0gcXVhbnRpbGUoeCA9IHN1YiRlcnIubiwgcHJvYnMgPSAwLjk3NSksIAogICAgICAgICAgY2xhc3MgPSAnVXRvcGlhIERpc3RhbmNlJywgdHlwID0gdHAsIG1ldGhvZCA9IG1ldCwKICAgICAgICAgIG4gPSBpIC0gbnJvdyhkYXRhLnBhcmV0KSwKICAgICAgICAgIHNhbXAgPSAnUmFuZG9tJykpCiAgICB9CiAgfQogIHJiaW5kKGVyci5hZGFwdCwgZXJyLnJhbmRvKQp9CnBhcmFsbGVsOjpzdG9wQ2x1c3RlcihjbCA9IG15LmNsdXN0ZXIpCiMgZXJyLm4KIyBDb21iaW5lIHdpdGggMCBzdGF0ZQplcnIwJG4gPSAwCmVycjAkY2xhc3MgPSAnVXRvcGlhIERpc3RhbmNlJwplcnIwJHNhbXAgPSAnQWRhcHRpdmUnCmVycjAkZXJyLm4gPSAxCmVycjAkdW5jLm1heCAgID0gZXJyMCRlcnI7IGVycjAkdW5jLm1pbiAgID0gZXJyMCRlcnIKZXJyMCR1bmMubWF4Lm4gPSAxOyAgICAgICAgZXJyMCR1bmMubWluLm4gPSAxCmVyci5uID0gcmJpbmQoZXJyMCwgZXJyLm4pCmVycjAkc2FtcCA9ICdSYW5kb20nCmVyci5uID0gcmJpbmQoZXJyMCwgZXJyLm4pCgojIFBsb3R0aW5nCiMgZXJyLm4KZ2dwbG90KGVyci5uKSArCiAgZ2VvbV9lcnJvcmJhcihtYXBwaW5nID0gYWVzKHggPSBuLCB5ID0gZXJyLm4sIHltaW4gPSB1bmMubWluLm4sIHltYXggPSB1bmMubWF4Lm4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gc2FtcCkpICsKICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeCA9IG4sIHkgPSBlcnIubiwgY29sb3IgPSBzYW1wKSkgKwogIGZhY2V0X2dyaWQodHlwfm1ldGhvZCwgc2NhbGUgPSAnZnJlZScpCmdncGxvdChlcnIubikgKwogIGdlb21fZXJyb3JiYXIobWFwcGluZyA9IGFlcyh4ID0gbiwgeSA9IGVyciwgeW1pbiA9IHVuYy5taW4sIHltYXggPSB1bmMubWF4LCBjb2xvciA9IHNhbXApKSArCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBuLCB5ID0gZXJyLCBjb2xvciA9IHNhbXApKSArCiAgZmFjZXRfZ3JpZCh0eXB+bWV0aG9kLCBzY2FsZSA9ICdmcmVlJykKCiMgU3RvcmUgZGF0YQplcnIuc3ZtID0gcmJpbmQoZXJyLnN2bSwgZXJyLm4pCgp3cml0ZS5jc3YoZXJyLnN2bSwgJ1NWTV9FcnJvci1Oc2FtcC5jc3YnLCByb3cubmFtZXMgPSBGKQpgYGAKCgpgYGB7ciBJbXBvcnRhbmNlIFJhbmtpbmcgTWV0aG9kIENvbXBhcmlzb246IENhbGN1bGF0aW5nIE1hcmdpbmFsIEltcG9ydGFuY2UgZm9yIFNWTX0KaW1wb3J0Lm1hcmdpbmFsID0gZnVuY3Rpb24oc3ZtLm1hcmdpbiwgdHlwKXsKICAjIFNldCB1cCBvdXRwdXQKICBpbXBvcnQuc3ZtLm1hcmdpbiA9IGRhdGEuZnJhbWUoKQogICMgTG9vcCBhY3Jvc3MgbWV0aG9kcwogIGZvcihtZXQgaW4gdW5pcXVlKHN2bS5tYXJnaW4kbWV0aG9kKSl7CiAgICBzdWIgPSBmaWx0ZXIoc3ZtLm1hcmdpbiwgY2F0ID09ICdhZGFwdCcsIHZhciA9PSAneDEnLCBtZXRob2QgPT0gbWV0KQogICAgaW1wb3J0LnN2bS5tYXJnaW4gPSByYmluZChpbXBvcnQuc3ZtLm1hcmdpbiwgZGF0YS5mcmFtZSgKICAgICAgaW1wb3J0ID0gZGlmZihyYW5nZShzdWIkcHJvYikpL3N1bShzdWIkcHNkKSwKICAgICAgdmFyID0gJ3gxJywKICAgICAgbWV0aG9kID0gcGFzdGUoJ01hcmdpbmFsJywgc3Vic3RyaW5nKG1ldCwgNCksIHNlcCA9ICcnKSwKICAgICAgc2QgPSBzcXJ0KChtYXgoc3ViJHByb2IpKigxLW1heChzdWIkcHJvYikpICsgbWluKHN1YiRwcm9iKSooMS1taW4oc3ViJHByb2IpKSAqIAogICAgICAgICAgICAgICAgICAgIGRpZmYocmFuZ2Uoc3ViJHByb2IpKSArIHZhcihzdWIkcHNkKSkvbnJvdyhzdWIpKQogICAgICApKQogICAgc3ViID0gZmlsdGVyKHN2bS5tYXJnaW4sIGNhdCA9PSAnYWRhcHQnLCB2YXIgPT0gJ3gyJywgbWV0aG9kID09IG1ldCkKICAgIGltcG9ydC5zdm0ubWFyZ2luID0gcmJpbmQoaW1wb3J0LnN2bS5tYXJnaW4sIGRhdGEuZnJhbWUoCiAgICAgIGltcG9ydCA9IGRpZmYocmFuZ2Uoc3ViJHByb2IpKS9zdW0oc3ViJHBzZCksCiAgICAgIHZhciA9ICd4MicsCiAgICAgIG1ldGhvZCA9IHBhc3RlKCdNYXJnaW5hbCcsIHN1YnN0cmluZyhtZXQsIDQpLCBzZXAgPSAnJyksCiAgICAgIHNkID0gc3FydCgobWF4KHN1YiRwcm9iKSooMS1tYXgoc3ViJHByb2IpKSArIG1pbihzdWIkcHJvYikqKDEtbWluKHN1YiRwcm9iKSkgKiAKICAgICAgICAgICAgICAgICAgICBkaWZmKHJhbmdlKHN1YiRwcm9iKSkgKyB2YXIoc3ViJHBzZCkpL25yb3coc3ViKSkKICAgICAgKSkKICB9CiAgIyBUeXBlLCByZWxhdGl2ZSBpbXBvcnRhbmNlCiAgaW1wb3J0LnN2bS5tYXJnaW4kdHlwID0gdHlwCiAgaW1wb3J0LnN2bS5tYXJnaW4kci5pbXBvcnQgPSBjKGltcG9ydC5zdm0ubWFyZ2luJGltcG9ydFsxOjJdL21heChpbXBvcnQuc3ZtLm1hcmdpbiRpbXBvcnRbMToyXSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGltcG9ydC5zdm0ubWFyZ2luJGltcG9ydFszOjRdL21heChpbXBvcnQuc3ZtLm1hcmdpbiRpbXBvcnRbMzo0XSkpCiAgcmV0dXJuKGltcG9ydC5zdm0ubWFyZ2luKQp9Cgpzdm0uaW1wb3J0Lm1hcmdpbiA9IHJiaW5kKGltcG9ydC5tYXJnaW5hbChzdm0ubWFyZ2luID0gcmVhZC5jc3YoJ01hcmdpbl9TVk1fZGlzdC5jc3YnKSwgdHlwID0gJ1BhcmV0byBEaXN0YW5jZScpLCAKICAgICAgaW1wb3J0Lm1hcmdpbmFsKHN2bS5tYXJnaW4gPSByZWFkLmNzdignTWFyZ2luX1NWTV9jdXRvZi5jc3YnKSwgdHlwID0gJ1RocmVzaG9sZCBDdXRvZmYnKSwgCiAgICAgIGltcG9ydC5tYXJnaW5hbChzdm0ubWFyZ2luID0gcmVhZC5jc3YoJ01hcmdpbl9TVk1fcmFkYW4uY3N2JyksIHR5cCA9ICdVdG9waWEgRGlzdGFuY2UnKSkKYGBgCgoKYGBge3IgU1ZNOiBJbXBvcnRhbmNlIFJhbmtpbmdzfQppbXBvcnQucmFuayA9IHJlYWQuY3N2KCcuLi9FeF9RdWFydGljL0ltcG9ydGFuY2UuY3N2JykKaW1wb3J0LnJhbmsgPSBpbXBvcnQucmFua1ssICFuYW1lcyhpbXBvcnQucmFuaykgJWluJSBjKCdYJyldCmltcG9ydC5yYW5rJG1ldGhvZCA9IHVubGlzdChsYXBwbHkoaW1wb3J0LnJhbmskbWV0aG9kLCBmdW5jdGlvbih4KSBwYXN0ZSh4LCAnLUdQJywgc2VwID0gJycpKSkKaW1wb3J0LnJhbmsKIyBpbXBvcnQucmFuayRtZXRob2RbaW1wb3J0LnJhbmskbWV0aG9kID09ICdTaGFwbGV5J10gPSAnU2hhcGxleS1HUCcKClNWTS5zaGFwLmRlbHRhID0gU1ZNLnNoYXAobW9kID0gbW9kLmFkYXB0LmRlbHRhKQpTVk0uc2hhcC5kZWx0YSR0eXAgPSAnUGFyZXRvIERpc3RhbmNlJwpTVk0uc2hhcC5jdXRvZiA9IFNWTS5zaGFwKG1vZCA9IG1vZC5hZGFwdC5jdXRvZikKU1ZNLnNoYXAuY3V0b2YkdHlwID0gJ1RocmVzaG9sZCBDdXRvZmYnClNWTS5zaGFwLnJhZGFuID0gU1ZNLnNoYXAobW9kID0gbW9kLmFkYXB0LnJhZGFuKQpTVk0uc2hhcC5yYWRhbiR0eXAgPSAnVXRvcGlhIERpc3RhbmNlJwoKCmdncGxvdChyYmluZChpbXBvcnQucmFuaywgU1ZNLnNoYXAuZGVsdGEsIFNWTS5zaGFwLmN1dG9mLCBTVk0uc2hhcC5yYWRhbiwgc3ZtLmltcG9ydC5tYXJnaW4pKSArCiAgZ2VvbV9jb2wobWFwcGluZyA9IGFlcyh4ID0gdmFyLCB5ID0gYWJzKHIuaW1wb3J0KSwgZmlsbCA9IHZhcikpICsKICBmYWNldF9ncmlkKG1ldGhvZH50eXAsIHNjYWxlcyA9ICdmcmVlX3knKSArCiAgbGFicyh4ID0gJycsIHkgPSAnUmVsYXRpdmUgSW1wb3J0YW5jZScpICsKICBndWlkZXMoZmlsbCA9IEZBTFNFKSArIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzID0gYygneDEnID0gZXhwcmVzc2lvbigneCdbMV0pLCAneDInID0gZXhwcmVzc2lvbigneCdbMl0pKSkKCmdncGxvdChyYmluZChpbXBvcnQucmFuaywgU1ZNLnNoYXAuZGVsdGEsIFNWTS5zaGFwLmN1dG9mLCBTVk0uc2hhcC5yYWRhbiwgc3ZtLmltcG9ydC5tYXJnaW4pKSArCiAgZ2VvbV9jb2wobWFwcGluZyA9IGFlcyh4ID0gdmFyLCB5ID0gaW1wb3J0LCBmaWxsID0gdmFyKSkgKwogIGZhY2V0X2dyaWQobWV0aG9kfnR5cCwgc2NhbGVzID0gJ2ZyZWVfeScpICsKICBsYWJzKHggPSAnJywgeSA9ICdSZWxhdGl2ZSBJbXBvcnRhbmNlJykgKwogIGd1aWRlcyhmaWxsID0gRkFMU0UpICsgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHMgPSBjKCd4MScgPSBleHByZXNzaW9uKCd4J1sxXSksICd4MicgPSBleHByZXNzaW9uKCd4J1syXSkpKQoKIyBSb3dzOiBTZWxlY3Rpb24gbWV0aG9kID0gUGFyZXRvIGRpc3RhbmNlLCBPYmplY3RpdmUgQ3V0b2ZmLCBVdG9waWEgRGlzdGFuY2UgKyBQcmlvcml0eQojIENvbHVtbnM6IG1ldGhvZCA9IFNoYXBsZXkgd2l0aCBTVk0sIFNoYXBsZXkgd2l0aCBHUCwgbmV3IG1ldGhvZAoKYGBgCgpgYGB7cn0KIyBTcGxpdCB1cCBieSBtb2RlbCBtZXRob2QKaW1wb3J0LmFsbCA9IHJiaW5kKGltcG9ydC5yYW5rLCBTVk0uc2hhcC5kZWx0YSwgU1ZNLnNoYXAuY3V0b2YsIFNWTS5zaGFwLnJhZGFuLCBzdm0uaW1wb3J0Lm1hcmdpbikKIyBDbGFzc2lmaWNhdGlvbiBzcGxpdApzcGxpdCA9IHVubGlzdChsYXBwbHkoaW1wb3J0LmFsbCRtZXRob2QsIHN0cnNwbGl0LCAnLScpKQppbXBvcnQuYWxsJG1ldGhvZCA9IHNwbGl0W2MoVFJVRSwgRkFMU0UpXQppbXBvcnQuYWxsJG1vZGVsID0gc3BsaXRbYyhGQUxTRSwgVFJVRSldCgppbXBvcnQuYWxsCmdncGxvdChpbXBvcnQuYWxsKSArCiAgZ2VvbV9jb2wobWFwcGluZyA9IGFlcyh4ID0gbW9kZWwsIHkgPSBpbXBvcnQsIGZpbGwgPSB2YXIpLCBwb3NpdGlvbiA9ICdkb2RnZScpICsKICBnZW9tX2Vycm9yYmFyKG1hcHBpbmcgPSBhZXMoeCA9IG1vZGVsLCB5bWF4ID0gaW1wb3J0ICsgc2QsIHltaW4gPSBpbXBvcnQgLSBzZCwgZ3JvdXAgPSB2YXIpLCAKICAgICAgICAgICAgICAgIHBvc2l0aW9uID0gJ2RvZGdlJywgd2lkdGggPSAwLjkpICsKICBmYWNldF9ncmlkKG1ldGhvZH50eXAsIHNjYWxlcyA9ICdmcmVlX3knKSArCiAgbGFicyh4ID0gJ01vZGVsJywgeSA9ICdSZWxhdGl2ZSBJbXBvcnRhbmNlIChVbml0bGVzcyknKSArCiAgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHMgPSBjKCdHUCcsICdwb2wnID0gJ1NWTS1wb2wnLCAncmFkJyA9ICdTVk0tcmFkJykpICsKICBzY2FsZV9maWxsX2Rpc2NyZXRlKG5hbWUgPSAnJywgbGFiZWxzID0gYygneDEnID0gZXhwcmVzc2lvbigneCdbMV0pLCAneDInID0gZXhwcmVzc2lvbigneCdbMl0pKSkKCiMgUmVsYXRpdmUgaW1wb3J0YW5jZSAobm9ybWFsaXplZCk6IHVuY2VydGFpbnR5IGlzIGRpdmlkZWQgYnkgdGhlIHNhbWUgdmFsdWUKci5tYXggPSBpbXBvcnQuYWxsJGltcG9ydApmb3IoaSBpbiBzZXEoZnJvbSA9IDEsIHRvID0gbGVuZ3RoKHIubWF4KS0xLCBieSA9IDIpKXsKICByLm1heFtjKGksaSsxKV0gPSBtYXgoYWJzKHIubWF4W2MoaSxpKzEpXSkpCn0KaW1wb3J0LmFsbCRyLmltcG9ydCA9IGFicyhpbXBvcnQuYWxsJGltcG9ydCkvci5tYXgKaW1wb3J0LmFsbCRyLnNkID0gaW1wb3J0LmFsbCRzZCAvIHIubWF4CmdncGxvdChpbXBvcnQuYWxsKSArCiAgZ2VvbV9jb2wobWFwcGluZyA9IGFlcyh4ID0gbW9kZWwsIHkgPSByLmltcG9ydCwgZmlsbCA9IHZhciksIHBvc2l0aW9uID0gJ2RvZGdlJykgKwogIGdlb21fZXJyb3JiYXIobWFwcGluZyA9IGFlcyh4ID0gbW9kZWwsIHltYXggPSByLmltcG9ydCArIHIuc2QsIHltaW4gPSByLmltcG9ydCAtIHIuc2QsIGdyb3VwID0gdmFyKSwgCiAgICAgICAgICAgICAgICBwb3NpdGlvbiA9ICdkb2RnZScsIHdpZHRoID0gMC45KSArCiAgZmFjZXRfZ3JpZChtZXRob2R+dHlwLCBzY2FsZXMgPSAnZnJlZV95JykgKwogIGxhYnMoeCA9ICdNb2RlbCcsIHkgPSAnUmVsYXRpdmUgSW1wb3J0YW5jZSAoVW5pdGxlc3MpJykgKwogIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzID0gYygnR1AnLCAncG9sJyA9ICdTVk0tcG9sJywgJ3JhZCcgPSAnU1ZNLXJhZCcpKSArCiAgc2NhbGVfZmlsbF9kaXNjcmV0ZShuYW1lID0gJycsIGxhYmVscyA9IGMoJ3gxJyA9IGV4cHJlc3Npb24oJ3gnWzFdKSwgJ3gyJyA9IGV4cHJlc3Npb24oJ3gnWzJdKSkpCgpybShyLm1heCkKCndyaXRlLmNzdihpbXBvcnQuYWxsLCAnSW1wb3J0UmFua19NZXRob2QuY3N2Jywgcm93Lm5hbWVzID0gRikKYGBgCgpJdCBpcyBjbGVhciB0aGF0IHRoZSBpbXBvcnRhbmNlIG1ldHJpYyB0aGF0IHdhcyBkZXZlbG9wZWQgaGVyZSBwcm92aWRlZCBtb3JlIGNvbnNpc3RlbnQgcmVzdWx0cyBmb3IgdGhlIGdsb2JhbCBpbXBvcnRhbmNlIHRoYW4gU2hhcGxleSB2YWx1ZXMuIApUaGUgaW1wb3J0YW5jZSBhbW9uZyBkaWZmZXJlbnQgTUwgbW9kZWxzIGdpdmVzIHNsaWdodGx5IGRpZmZlcmVudCByZXN1bHRzLCBzdWdnZXN0aW5nIGRpZmZlcmVuY2VzIGluIHRoZSBtb2RlbHMgbW9yZSB0aGFuIHByb2JsZW1zIHdpdGggdGhlIGltcG9ydGFuY2UgbWV0cmljLgoKCiMgRXhpc3RpbmcgTWV0aG9kOiBHYXVzc2lhbiBNaXh0dXJlcwoKU2luY2UgdGhlIEdNIG1vZGVscyBhcmUgdW5zdXBlcnZpc2VkLCB0aGVyZSBjYW5ub3QgYmUgY29udHJvbCBmb3Igd2hhdCB0aGUgc2VsZWN0aW9uIGNyaXRlcmlhIGFyZS4gQSBzdXBlcmZpY2lhbCBhbmFseXNpcyB3aWxsIGJlIGNvbmR1Y3RlZCB0byBjaGVjayB3aGF0IHRoZSBNTCBhbGdvcml0aG0gaXMgY29udmVyZ2luZyB0bywgYnV0IHRoZSByZXN1bHRzIHdpbGwgYmUgaW50ZXJwcmV0ZWQgc29sZWx5IGluIHRlcm1zIG9mIGRlc2NyaXB0aXZlIHN0YXRpc3RpY3MuCgpGb3IgdGhlIHB1cnBvc2VzIG9mIGFuYWx5c2lzLCB0aHJlZSBtb2RlbHMgd2lsbCBiZSB0ZXN0ZWQgYmFzZWQgb24gdGhlIGlucHV0IHRvIHRoZSBNTCBhbGdvcml0aG06CiogKHgxLCB4MiwgZjEsIGYyKTogdGhlIGZ1bGwgc2V0IG9mIGlucHV0cyBhbmQgb3V0cHV0cyAtIHNpbmNlIGl0IGhhcyB0aGUgbW9zdCBkYXRhLCB0aGlzIHdpbGwgbGlrZWx5IGJlIHRoZSBtb3N0IHJvYnVzdAoqICh4MSwgeDIpOiBuZWdhdGl2ZSBjb250cm9sIG9mIGp1c3QgdGhlIGlucHV0cyAtIGl0IHNob3VsZCBncm91cCB0aGUgcG9pbnRzIGJhc2VkIG9uIHRoZSBzYW1wbGluZyBkZW5zaXRpZXMKKiAoZjEsIGYyKTogb3V0cHV0cyBvbmx5IC0gdGhpcyBzaG91bGQgaGF2ZSB0aGUgYmVzdCBkaXN0aW5jdGlvbiBiZXR3ZWVuIGdvb2QgYW5kIGJhZCBwZXJmb3JtaW5nIGdyb3VwcwoKVXAgdG8gMTIgY2F0ZWdvcmllcyB3aWxsIGJlIGFsbG93ZWQsIGFuZCBhbnkgdHlwZSBvZiBHYXVzc2lhbiBtb2RlbHMgd2lsbCBiZSBhY2NlcHRlZC4gVGhpcyB3aWxsIGxlYWQgdG8gbG9uZ2VyIGZpdHRpbmcgdGltZSwgYnV0IHNob3VsZCBoZWxwIHdpdGggYWNjdXJhY3kuCgpgYGB7ciBHTTogR2VuZXJhdGluZyBtb2RlbHMsIHJlc3VsdHM9J2hpZGUnfQpHUGFyLmFsbCA9IHJlYWQuY3N2KGZpbGUgPSAnLi4vRXhfUXVhcnRpYy9HUGFyX2FsbF9zdGFydC5jc3YnKQoKIyBuYW1lcyhHUGFyLmFsbCkKbWF4LmNhdCA9IDEyCmNsdXN0ZXIuYWxsID0gTWNsdXN0KGRhdGEgPSBHUGFyLmFsbFssYygneDEnLCAneDInLCAnZjEnLCAnZjInKV0sIEcgPSAyOm1heC5jYXQpCmNsdXN0ZXIuaW4gPSBNY2x1c3QoZGF0YSA9IEdQYXIuYWxsWyxjKCd4MScsICd4MicpXSwgRyA9IDI6bWF4LmNhdCkKY2x1c3Rlci5vdXQgPSBNY2x1c3QoZGF0YSA9IEdQYXIuYWxsWyxjKCdmMScsICdmMicpXSwgRyA9IDI6bWF4LmNhdCkKCmBgYAoKYGBge3IgR006IFBsb3R0aW5nIG1vZGVsIG91dHB1dHN9CiMgc3VtbWFyeShjbHVzdGVyLCBwYXJhbWV0ZXJzID0gVFJVRSkKCkdQYXIuYWxsJHR5cC5hbGwgPSBjbHVzdGVyLmFsbCRjbGFzc2lmaWNhdGlvbgpHUGFyLmFsbCR0eXAuaW4gPSBjbHVzdGVyLmluJGNsYXNzaWZpY2F0aW9uCkdQYXIuYWxsJHR5cC5vdXQgPSBjbHVzdGVyLm91dCRjbGFzc2lmaWNhdGlvbgoKZ2dwbG90KCkgKwogICMgQm91bmRhcmllczogKy8tIHNvbWUgc2VwYXJhdGlvbiBmcm9tIDAuNQogIGdlb21fY29udG91cihkYXRhID0gZmluZS5ncmlkLCBtYXBwaW5nID0gYWVzKHggPSB4MSwgeSA9IHgyLCB6ID0gZGlzdCwgY29sb3IgPSAnZGVsdGEnKSwgYnJlYWtzID0gYygxKSkgKwogIGdlb21fY29udG91cihkYXRhID0gZmluZS5ncmlkLCBtYXBwaW5nID0gYWVzKHggPSB4MSwgeSA9IHgyLCB6ID0gY3V0b2YsIGNvbG9yID0gJ2N1dG9mJyksIGJyZWFrcyA9IGMoMC41KSkgKwogIGdlb21fY29udG91cihkYXRhID0gZmluZS5ncmlkLCBtYXBwaW5nID0gYWVzKHggPSB4MSwgeSA9IHgyLCB6ID0gcmFkLCBjb2xvciA9ICdyYWQnKSwgYnJlYWtzID0gYygxKSkgKwogIGdlb21fY29udG91cihkYXRhID0gZmluZS5ncmlkLCBtYXBwaW5nID0gYWVzKHggPSB4MSwgeSA9IHgyLCB6ID0gYW5nLCBjb2xvciA9ICdhbmcnKSwgYnJlYWtzID0gYyg1MCkpICsKICAjIFBhcmV0byBmcm9udGllcgogIGdlb21fc21vb3RoKGRhdGEgPSBHUGFyLmZyb250LCBtYXBwaW5nID0gYWVzKHggPSB4MSwgeSA9IHgyLCBjb2xvciA9ICdQYXJldG8nKSwgCiAgICAgICAgICAgICAgbGV2ZWwgPSAwLjk1LCBmb3JtdWxhID0gKHl+eCksIG1ldGhvZCA9ICdsb2VzcycpICsgCiAgIyBDbHVzdGVyaW5nCiAgZ2VvbV9wb2ludChkYXRhID0gR1Bhci5hbGwsIG1hcHBpbmcgPSBhZXMoeCA9IHgxLCB5ID0geDIsIGZpbGwgPSBhcy5mYWN0b3IodHlwLmFsbCkpLCBzaXplID0gMi41LCBzaGFwZSA9IDIxKSArCgogIGxhYnMoeCA9IGV4cHJlc3Npb24oJ3gnWzFdKSwgeSA9IGV4cHJlc3Npb24oJ3gnWzJdKSwgCiAgICAgICBjb2xvciA9ICdBY2NlcHRhbmNlIENyaXRlcmlhJywgZmlsbCA9ICdHcm91cCcsIAogICAgICAgc3VidGl0bGUgPSAnKHgxLCB4MiwgZjEsIGYyKScpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygnZGVsdGEnID0gJyMxYjllNzcnLCAnY3V0b2YnID0gJyNkOTVmMDInLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAncmFkJyA9ICcjNzU3MGIzJywgJ2FuZycgPSAnI2U3Mjk4YScsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdQYXJldG8nID0gJ2JsYWNrJyksCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoJ2RlbHRhJyA9IGV4cHJlc3Npb24oZGVsdGEqJyA8IDEnKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ2N1dG9mJyA9IGV4cHJlc3Npb24oJ0YnWzFdXicqJyonPCAxLCBGJ1syXV4nKicqJzwgMScpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAncmFkJyA9IGV4cHJlc3Npb24oJ3IgPCAxJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ2FuZycgPSBleHByZXNzaW9uKCdGJ1sxXSonYW5kIEYnWzJdKicgQmFsYW5jZScpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdQYXJldG8nID0gZXhwcmVzc2lvbignUGFyZXRvIEZyb250JykpLAogICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBjKCdkZWx0YScsICdjdXRvZicsICdyYWQnLCAnYW5nJywgJ1BhcmV0bycpKSArCiAgdGhlbWVfY2xhc3NpYygpICsgI3RoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC44NSwgMC43NSkpICsgCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGxpbWl0cyA9IGMoMCwgNSkpICsgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGxpbWl0cyA9IGMoMCwgNSkpICsKICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3QoZmlsbCA9IGFscGhhKCd3aGl0ZScsIDEpKSkpCgpnZ3Bsb3QoKSArCiAgIyBCb3VuZGFyaWVzOiArLy0gc29tZSBzZXBhcmF0aW9uIGZyb20gMC41CiAgZ2VvbV9jb250b3VyKGRhdGEgPSBmaW5lLmdyaWQsIG1hcHBpbmcgPSBhZXMoeCA9IHgxLCB5ID0geDIsIHogPSBkaXN0LCBjb2xvciA9ICdkZWx0YScpLCBicmVha3MgPSBjKDEpKSArCiAgZ2VvbV9jb250b3VyKGRhdGEgPSBmaW5lLmdyaWQsIG1hcHBpbmcgPSBhZXMoeCA9IHgxLCB5ID0geDIsIHogPSBjdXRvZiwgY29sb3IgPSAnY3V0b2YnKSwgYnJlYWtzID0gYygwLjUpKSArCiAgZ2VvbV9jb250b3VyKGRhdGEgPSBmaW5lLmdyaWQsIG1hcHBpbmcgPSBhZXMoeCA9IHgxLCB5ID0geDIsIHogPSByYWQsIGNvbG9yID0gJ3JhZCcpLCBicmVha3MgPSBjKDEpKSArCiAgZ2VvbV9jb250b3VyKGRhdGEgPSBmaW5lLmdyaWQsIG1hcHBpbmcgPSBhZXMoeCA9IHgxLCB5ID0geDIsIHogPSBhbmcsIGNvbG9yID0gJ2FuZycpLCBicmVha3MgPSBjKDUwKSkgKwogICMgUGFyZXRvIGZyb250aWVyCiAgZ2VvbV9zbW9vdGgoZGF0YSA9IEdQYXIuZnJvbnQsIG1hcHBpbmcgPSBhZXMoeCA9IHgxLCB5ID0geDIsIGNvbG9yID0gJ1BhcmV0bycpLCAKICAgICAgICAgICAgICBsZXZlbCA9IDAuOTUsIGZvcm11bGEgPSAoeX54KSwgbWV0aG9kID0gJ2xvZXNzJykgKyAKICAjIENsdXN0ZXJpbmcKICBnZW9tX3BvaW50KGRhdGEgPSBHUGFyLmFsbCwgbWFwcGluZyA9IGFlcyh4ID0geDEsIHkgPSB4MiwgZmlsbCA9IGFzLmZhY3Rvcih0eXAuaW4pKSwgc2l6ZSA9IDIuNSwgc2hhcGUgPSAyMSkgKwoKICBsYWJzKHggPSBleHByZXNzaW9uKCd4J1sxXSksIHkgPSBleHByZXNzaW9uKCd4J1syXSksIAogICAgICAgY29sb3IgPSAnQWNjZXB0YW5jZSBDcml0ZXJpYScsIGZpbGwgPSAnR3JvdXAnLCAKICAgICAgIHN1YnRpdGxlID0gJyh4MSwgeDIpJykgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCdkZWx0YScgPSAnIzFiOWU3NycsICdjdXRvZicgPSAnI2Q5NWYwMicsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdyYWQnID0gJyM3NTcwYjMnLCAnYW5nJyA9ICcjZTcyOThhJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1BhcmV0bycgPSAnYmxhY2snKSwKICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygnZGVsdGEnID0gZXhwcmVzc2lvbihkZWx0YSonIDwgMScpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnY3V0b2YnID0gZXhwcmVzc2lvbignRidbMV1eJyonKic8IDEsIEYnWzJdXicqJyonPCAxJyksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdyYWQnID0gZXhwcmVzc2lvbignciA8IDEnKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnYW5nJyA9IGV4cHJlc3Npb24oJ0YnWzFdKidhbmQgRidbMl0qJyBCYWxhbmNlJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1BhcmV0bycgPSBleHByZXNzaW9uKCdQYXJldG8gRnJvbnQnKSksCiAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IGMoJ2RlbHRhJywgJ2N1dG9mJywgJ3JhZCcsICdhbmcnLCAnUGFyZXRvJykpICsKICB0aGVtZV9jbGFzc2ljKCkgKyAjdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYygwLjg1LCAwLjc1KSkgKyAKICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgbGltaXRzID0gYygwLCA1KSkgKyBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgbGltaXRzID0gYygwLCA1KSkgKwogIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChmaWxsID0gYWxwaGEoJ3doaXRlJywgMSkpKSkKCmdncGxvdCgpICsKICAjIEJvdW5kYXJpZXM6ICsvLSBzb21lIHNlcGFyYXRpb24gZnJvbSAwLjUKICBnZW9tX2NvbnRvdXIoZGF0YSA9IGZpbmUuZ3JpZCwgbWFwcGluZyA9IGFlcyh4ID0geDEsIHkgPSB4MiwgeiA9IGRpc3QsIGNvbG9yID0gJ2RlbHRhJyksIGJyZWFrcyA9IGMoMSkpICsKICBnZW9tX2NvbnRvdXIoZGF0YSA9IGZpbmUuZ3JpZCwgbWFwcGluZyA9IGFlcyh4ID0geDEsIHkgPSB4MiwgeiA9IGN1dG9mLCBjb2xvciA9ICdjdXRvZicpLCBicmVha3MgPSBjKDAuNSkpICsKICBnZW9tX2NvbnRvdXIoZGF0YSA9IGZpbmUuZ3JpZCwgbWFwcGluZyA9IGFlcyh4ID0geDEsIHkgPSB4MiwgeiA9IHJhZCwgY29sb3IgPSAncmFkJyksIGJyZWFrcyA9IGMoMSkpICsKICBnZW9tX2NvbnRvdXIoZGF0YSA9IGZpbmUuZ3JpZCwgbWFwcGluZyA9IGFlcyh4ID0geDEsIHkgPSB4MiwgeiA9IGFuZywgY29sb3IgPSAnYW5nJyksIGJyZWFrcyA9IGMoNTApKSArCiAgIyBQYXJldG8gZnJvbnRpZXIKICBnZW9tX3Ntb290aChkYXRhID0gR1Bhci5mcm9udCwgbWFwcGluZyA9IGFlcyh4ID0geDEsIHkgPSB4MiwgY29sb3IgPSAnUGFyZXRvJyksIAogICAgICAgICAgICAgIGxldmVsID0gMC45NSwgZm9ybXVsYSA9ICh5fngpLCBtZXRob2QgPSAnbG9lc3MnKSArIAogICMgQ2x1c3RlcmluZwogIGdlb21fcG9pbnQoZGF0YSA9IEdQYXIuYWxsLCBtYXBwaW5nID0gYWVzKHggPSB4MSwgeSA9IHgyLCBmaWxsID0gYXMuZmFjdG9yKHR5cC5vdXQpKSwgc2l6ZSA9IDIuNSwgc2hhcGUgPSAyMSkgKwoKICBsYWJzKHggPSBleHByZXNzaW9uKCd4J1sxXSksIHkgPSBleHByZXNzaW9uKCd4J1syXSksIAogICAgICAgY29sb3IgPSAnQWNjZXB0YW5jZSBDcml0ZXJpYScsIGZpbGwgPSAnR3JvdXAnLCAKICAgICAgIHN1YnRpdGxlID0gJyhmMSwgZjIpJykgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCdkZWx0YScgPSAnIzFiOWU3NycsICdjdXRvZicgPSAnI2Q5NWYwMicsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdyYWQnID0gJyM3NTcwYjMnLCAnYW5nJyA9ICcjZTcyOThhJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1BhcmV0bycgPSAnYmxhY2snKSwKICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygnZGVsdGEnID0gZXhwcmVzc2lvbihkZWx0YSonIDwgMScpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnY3V0b2YnID0gZXhwcmVzc2lvbignRidbMV1eJyonKic8IDEsIEYnWzJdXicqJyonPCAxJyksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdyYWQnID0gZXhwcmVzc2lvbignciA8IDEnKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnYW5nJyA9IGV4cHJlc3Npb24oJ0YnWzFdKidhbmQgRidbMl0qJyBCYWxhbmNlJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1BhcmV0bycgPSBleHByZXNzaW9uKCdQYXJldG8gRnJvbnQnKSksCiAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IGMoJ2RlbHRhJywgJ2N1dG9mJywgJ3JhZCcsICdhbmcnLCAnUGFyZXRvJykpICsKICB0aGVtZV9jbGFzc2ljKCkgKyAjdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYygwLjg1LCAwLjc1KSkgKyAKICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgbGltaXRzID0gYygwLCA1KSkgKyBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgbGltaXRzID0gYygwLCA1KSkgKwogIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChmaWxsID0gYWxwaGEoJ3doaXRlJywgMSkpKSkKCmBgYAoKVGhlIGNvbXBsZXRlIGlucHV0ICh4MSwgeDIsIGYxLCBmMikgb3ZlcmNvbXBsaWNhdGVzIHRoZSBzeXN0ZW0sIHByb3ZpZGluZyA4IGdyb3Vwcy4gT25lIG9mIHRoZXNlIGdyb3VwcyBpcyBjbGVhcmx5IHRoZSBQYXJldG8gZnJvbnRpZXIsIGJ1dCBpdCBkb2VzIG5vdCBpbmNsdWRlIGFueSBwb2ludHMgdGhhdCBhcmUgb2Ygc2ltaWxhciBwZXJmb3JtYW5jZS4gVGhlcmUgYXJlIHBvaW50cyB0byBlaXRoZXIgc2lkZSBvZiBpdCBzdWdnZXN0aW5nIHNpbWlsYXIgcGVyZm9ybWFuY2UsIGJ1dCBob3cgZmFyIHRoZXkgZXh0ZW5kIGlzIGRpZmZpY3VsdCB0byBpbnRlcnByZXQuCgpUaGUgbmVnYXRpdmUgY29udHJvbCAoeDEsIHgyKSBnaXZlcyB0aGUgZXhwZWN0ZWQgcmVzdWx0IG9mIGxhcmdlbHkgZ3JvdXBpbmcgYmFzZWQgb24gc2FtcGxpbmcgZGVuc2l0eS4gVGhpcyBtZWFucyB0aGUgaGlnaGx5IHNhbXBsZWQgUGFyZXRvIGZyb250aWVyIGFuZCBsb2NhbCBtaW5pbXVtIGFyZSB0aGVpciBvd24gZ3JvdXBzLCBhbmQgdGhlIHJlc3Qgb2YgdGhlIHNwYWNlIGlzIGRpdmlkZWQgYmFzZWQgYXJvdW5kIHRoZSBib3VuZGFyeSBiZXR3ZWVuIHRoZSBoaWdobHkgc2FtcGxlZCByZWdpb25zLgoKVGhlIG91dHB1dC1vbmx5IG1vZGVsIGdpdmVzIHRoZSBtb3N0IHVzZWZ1bCByZXN1bHRzLCBhcyBpdCBncm91cHMgdGhlIFBhcmV0byBmcm9udGllciBpbnNpZGUgb2YgYW5vdGhlciBoaWdoLXBlcmZvcm1pbmcgZ3JvdXAsIHdoaWNoIGFsc28gaW5jbHVkZXMgdGhlIGxvY2FsIG9wdGltdW0gYW5kIHRoZSBzcGFjZSBzcGFubmluZyB0byBpdC4gSXQgcm91Z2hseSBsaW5lcyB1cCB3aXRoIHRoZSBQYXJldG8gZGlzdGFuY2Ugb3IgdGhyZXNob2xkIGNyaXRlcmlhOyBpdCBkb2VzIG5vdCBhcHBlYXIgdG8gcHJpb3JpdGl6ZSB0aGUgYW5nbGUgb3IgdXRvcGlhIGRpc3RhbmNlLiBTaG93aW5nIG9ubHkgdGhpcyBtb2RlbCBhdCBmaW5lciByZXNvbHV0aW9uIGFzIGl0IGlzIHRoZSBvbmx5IHJlbGV2YW50IHBlcmZvcm1pbmcgbW9kZWwKCmBgYHtyfQpyZXMgPSBwcmVkaWN0KG9iamVjdCA9IGNsdXN0ZXIub3V0LCBuZXdkYXRhID0gZmluZS5ncmlkW2MoJ2YxJywgJ2YyJyldKQpmaW5lLmdyaWQkY2x1c3Rlci5vdXQgPSByZXMkY2xhc3NpZmljYXRpb24KZ2dwbG90KCkgKwogICMgQ2x1c3RlciByZXN1bHRzCiAgZ2VvbV9wb2ludChkYXRhID0gZmluZS5ncmlkLCBtYXBwaW5nID0gYWVzKHggPSB4MSwgeSA9IHgyLCBjb2xvciA9IGFzLmZhY3RvcihjbHVzdGVyLm91dCkpKSArCiAgIyBCb3VuZGFyaWVzOiArLy0gc29tZSBzZXBhcmF0aW9uIGZyb20gMC41CiAgZ2VvbV9jb250b3VyKGRhdGEgPSBmaW5lLmdyaWQsIG1hcHBpbmcgPSBhZXMoeCA9IHgxLCB5ID0geDIsIHogPSBkaXN0KSwgY29sb3IgPSAnYmxhY2snLCBicmVha3MgPSBjKDEpKSArCiAgZ2VvbV9jb250b3VyKGRhdGEgPSBmaW5lLmdyaWQsIG1hcHBpbmcgPSBhZXMoeCA9IHgxLCB5ID0geDIsIHogPSBjdXRvZiksIGNvbG9yID0gJ3JlZCcsIGJyZWFrcyA9IGMoMC41KSkgKwoKICBsYWJzKHggPSBleHByZXNzaW9uKCd4J1sxXSksIHkgPSBleHByZXNzaW9uKCd4J1syXSksIAogICAgICAgY29sb3IgPSAnR3JvdXAnLCBmaWxsID0gJ0dyb3VwJywgCiAgICAgICBzdWJ0aXRsZSA9ICcoZjEsIGYyKScpICsKICB0aGVtZV9jbGFzc2ljKCkgKyAjdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYygwLjg1LCAwLjc1KSkgKyAKICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgbGltaXRzID0gYygwLCA1KSkgKyBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgbGltaXRzID0gYygwLCA1KSkgKwogIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChmaWxsID0gYWxwaGEoJ3doaXRlJywgMSkpKSkKCnJlcyA9IHByZWRpY3Qob2JqZWN0ID0gY2x1c3Rlci5hbGwsIG5ld2RhdGEgPSBmaW5lLmdyaWRbYygneDEnLCAneDInLCAnZjEnLCAnZjInKV0pCmZpbmUuZ3JpZCRjbHVzdGVyLmFsbCA9IHJlcyRjbGFzc2lmaWNhdGlvbgpnZ3Bsb3QoKSArCiAgIyBDbHVzdGVyIHJlc3VsdHMKICBnZW9tX3BvaW50KGRhdGEgPSBmaW5lLmdyaWQsIG1hcHBpbmcgPSBhZXMoeCA9IHgxLCB5ID0geDIsIGNvbG9yID0gYXMuZmFjdG9yKGNsdXN0ZXIuYWxsKSksIHNpemUgPSA0KSArCiAgIyBCb3VuZGFyaWVzOiArLy0gc29tZSBzZXBhcmF0aW9uIGZyb20gMC41CiAgZ2VvbV9jb250b3VyKGRhdGEgPSBmaW5lLmdyaWQsIG1hcHBpbmcgPSBhZXMoeCA9IHgxLCB5ID0geDIsIHogPSBkaXN0KSwgY29sb3IgPSAnYmxhY2snLCBicmVha3MgPSBjKDEpKSArCiAgZ2VvbV9jb250b3VyKGRhdGEgPSBmaW5lLmdyaWQsIG1hcHBpbmcgPSBhZXMoeCA9IHgxLCB5ID0geDIsIHogPSBjdXRvZiksIGNvbG9yID0gJ3JlZCcsIGJyZWFrcyA9IGMoMC41KSkgKwoKICBsYWJzKHggPSBleHByZXNzaW9uKCd4J1sxXSksIHkgPSBleHByZXNzaW9uKCd4J1syXSksIAogICAgICAgY29sb3IgPSAnR3JvdXAnLCBmaWxsID0gJ0dyb3VwJywgCiAgICAgICBzdWJ0aXRsZSA9ICcoeDEsIHgyLCBmMSwgZjIpJykgKwogIHRoZW1lX2NsYXNzaWMoKSArICN0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuODUsIDAuNzUpKSArIAogIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBsaW1pdHMgPSBjKDAsIDUpKSArIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBsaW1pdHMgPSBjKDAsIDUpKSArCiAgZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KGZpbGwgPSBhbHBoYSgnd2hpdGUnLCAxKSkpKQoKZ2dwbG90KCkgKwogICMgQ2x1c3RlciByZXN1bHRzCiAgZ2VvbV9wb2ludChkYXRhID0gZmlsdGVyKGZpbmUuZ3JpZCwgZjIgPCA1MCwgZjEgPCAyMDApLCBtYXBwaW5nID0gYWVzKHggPSBmMSwgeSA9IGYyLCBjb2xvciA9IGFzLmZhY3RvcihjbHVzdGVyLmFsbCkpLCBzaXplID0gMikgKwogIGxhYnMoeCA9IGV4cHJlc3Npb24oJ2YnWzFdKSwgeSA9IGV4cHJlc3Npb24oJ2YnWzJdKSwgCiAgICAgICBjb2xvciA9ICdHcm91cCcsIGZpbGwgPSAnR3JvdXAnLCAKICAgICAgIHN1YnRpdGxlID0gJyh4MSwgeDIsIGYxLCBmMiknKSArCiAgdGhlbWVfY2xhc3NpYygpICsKICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3QoZmlsbCA9IGFscGhhKCd3aGl0ZScsIDEpKSkpCgpgYGAKClRoZSAoZjEsIGYyKSBtb2RlbCBhcHBlYXJzIHRvIGdpdmUgYSBjbHVzdGVyIHRoYXQgaXMgc29tZXdoZXJlIGJldHdlZW4gYSB0aHJlc2hvbGQgY3V0b2ZmIGFuZCB0aGUgUGFyZXRvIGRpc3RhbmNlIGN1dG9mZi4gCkluIGNvbnRyYXN0LCB0aGUgYmVoYXZpb3Igb2YgdGhlICh4MSwgeDIsIGYxLCBmMikgbW9kZWwgcHJvdmlkZXMgbWFueSBtb3JlIGdyb3VwcywgaW5jbHVkaW5nIHNwbGl0dGluZyB0aGUgUGFyZXRvIGZyb250IGludG8gYWJvdXQgdGhyZWUgZGlmZmVyZW50IGNhdGVnb3JpZXMgd2l0aCBubyBvYnZpb3VzIGFuYWxvZyB0byB3aHkgdGhlIGJvdW5kYXJpZXMgYXJlIHdoZXJlIHRoZXkgYXJlLiAKSXQgYXBwZWFycyB0byByb3VnaGx5IGZpdCB0aGUgc2FtZSBib3VuZGFyaWVzIG9mIFBhcmV0byBkaXN0YW5jZSBvciB0aHJlc2hvbGQgY3V0b2ZmcywgYnV0IG5vdCB2ZXJ5IHdlbGwuIApBIHJvdWdoIGFwcHJveGltYXRpb24gaXMgdGhhdCBncm91cHMgKDUsIDYpIGFyZSB0aGUgUGFyZXRvIGZyb250LCBncm91cHMgKDIsIDMsIDcpIG1ha2UgdXAgdGhlIHJlZ2lvbiBjbG9zZSB0byB0aGUgUGFyZXRvIGZyb250LCBhbmQgKDEsIDQsIDgpIGFyZSB0aGUgcmVnaW9uIGZhciBmcm9tIHRoZSBmcm9udC4KClRoZSBtb2RlbCBmb3IgKGYxLCBmMikgaXMgZ29pbmcgdG8gZ2l2ZSB0aGUgc2FtZSBjb25kaXRpb25hbCBwcm9iYWJpbGl0aWVzIGFzIHRoZSBQYXJldG8gZGlzdGFuY2Ugb3IgdGhyZXNob2xkIGFjY2VwdGFuY2UgY3JpdGVyaWEgYmFzZWQgb24gdGhpcyBzaW1pbGFyaXR5IGluIHRoZSBzaGFwZSBvZiB0aGUgYm91bmRhcnkuIApUaGUgaW50ZXJlc3RpbmcgcmVzdWx0IHRvIGludGVycHJldCBpcyB0aGUgKHgxLCB4MiwgZjEsIGYyKSwgcGFydGljdWxhcmx5IHdoZW4gbWFyZ2luYWxpemluZyB0byAoeDEsIHgyKS4gCkFzc3VtaW5nIHRoYXQgY2FsY3VsYXRpbmcgKGYxLCBmMikgaXMgZXhwZW5zaXZlLCB0aGUgYmVzdCBhcHByb3hpbWF0aW9uIGlzIHRoYXQgZm91bmQgZnJvbSB0aGUgR1AgbW9kZWxzIHVzZWQgdG8gZmluZCB0aGUgUGFyZXRvIGZyb250IGl0c2VsZi4gClRoZXNlIHdpbGwgZ2l2ZSAoZjEsIGYyKSBhcyBhIGJpdmFyaWF0ZSBHYXVzc2lhbiBkaXN0cmlidXRpb24sIHdoaWNoIGNhbiBiZSBzYW1wbGVkIGZyb20gdG8gZXN0aW1hdGUgdGhlIGxpa2VsaWhvb2QgdGhhdCBpdCBmYWxscyBpbnRvIHRoZSBQYXJldG8gZnJvbnQsIHRoZSByZWdpb24gY2xvc2UgdG8gaXQsIG9yIHRoZSByZWdpb24gZmFyIGZyb20gaXQuClRoaXMgaXMgYWNoaWV2YWJsZSB3aXRoIGEgc2VxdWVudGlhbCBNb250ZSBDYXJsbzogZ2l2ZW4geDEsIHNhbXBsZSB4MiBmcm9tIHRoZSByYW5nZSwgZmluZCB0aGUgZGlzdHJpYnV0aW9uIG9mIChmMSwgZjIpLCBzYW1wbGUgKGYxLCBmMiksIGFuZCBzb2x2ZSB0aGUgY2xhc3NpZmljYXRpb24gaW50byB0aGUgdGhyZWUgZ3JvdXBzLgoKYGBge3J9CmYxLm1vZCA9IGZpbGwuc2FtcGxlLm1vZChHUGFyLmRhdGEgPSBHUGFyLmFsbCwgaW5wdXQubmFtZSA9IGMoJ3gxJywgJ3gyJyksIG91dHB1dC5uYW1lID0gJ2YxJykKZjIubW9kID0gZmlsbC5zYW1wbGUubW9kKEdQYXIuZGF0YSA9IEdQYXIuYWxsLCBpbnB1dC5uYW1lID0gYygneDEnLCAneDInKSwgb3V0cHV0Lm5hbWUgPSAnZjInKQoKeC5ybmcgPSBzZXEoZnJvbSA9IDAsIHRvID0gNSwgbGVuZ3RoLm91dCA9IDUwKQpuc2FtcC54ID0gMTAwOyBuc2FtcC5mID0gMTAwCmdtLm1hcmdpbiA9IGRhdGEuZnJhbWUoKQpmb3IoeCBpbiB4LnJuZyl7CiAgIyB4MQogICMgU2FtcGxlIHgyCiAgaW5mcmFtZSA9IGRhdGEuZnJhbWUoeDEgPSB4LCB4MiA9IHJ1bmlmKG4gPSBuc2FtcC54LCBtaW4gPSAwLCBtYXggPSA1KSkKICBjbGFzc2VzID0gYygpCiAgZm9yKG4gaW4gMTpucm93KGluZnJhbWUpKXsKICAgICMgRXN0aW1hdGUgZGlzdHJpYnV0aW9uIGZvciBmMSwgZjIKICAgIGYxLmVzdCA9IHByZWRpY3Qob2JqZWN0ID0gZjEubW9kLCBuZXdkYXRhID0gaW5mcmFtZSwgdHlwZSA9ICdVSycpCiAgICBmMi5lc3QgPSBwcmVkaWN0KG9iamVjdCA9IGYyLm1vZCwgbmV3ZGF0YSA9IGluZnJhbWUsIHR5cGUgPSAnVUsnKQogICAgdGVzdC5mcmFtZSA9IGRhdGEuZnJhbWUoeDEgPSBpbmZyYW1lJHgxLCB4MiA9IGluZnJhbWUkeDIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBmMSA9IHJub3JtKG4gPSBuc2FtcC5mLCBtZWFuID0gZjEuZXN0JG1lYW4sIHNkID0gZjEuZXN0JHNkKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGYyID0gcm5vcm0obiA9IG5zYW1wLmYsIG1lYW4gPSBmMi5lc3QkbWVhbiwgc2QgPSBmMi5lc3Qkc2QpKQogICAgcmVzID0gcHJlZGljdChvYmplY3QgPSBjbHVzdGVyLmFsbCwgbmV3ZGF0YSA9IHRlc3QuZnJhbWUpCiAgICBjbGFzc2VzID0gYyhjbGFzc2VzLCByZXMkY2xhc3NpZmljYXRpb24pCiAgfQogIGNsYXNzZXMgPSBkYXRhLmZyYW1lKGdyb3VwID0gY2xhc3NlcykKICBnbS5tYXJnaW4gPSByYmluZChnbS5tYXJnaW4sIGRhdGEuZnJhbWUoeCA9IHgsIHZhciA9ICd4MScsIAogICAgICAgICAgICAgICAgICAgICAgICAgcC5wYXJlID0gbnJvdyhmaWx0ZXIoY2xhc3NlcywgZ3JvdXAgPT0gNSB8IGdyb3VwID09IDYpKS8obnNhbXAuZipuc2FtcC54KSwgCiAgICAgICAgICAgICAgICAgICAgICAgICBwLm5lYXIgPSBucm93KGZpbHRlcihjbGFzc2VzLCBncm91cCA9PSAyIHwgZ3JvdXAgPT0gMyB8IGdyb3VwID09IDcpKS8obnNhbXAuZipuc2FtcC54KSwgCiAgICAgICAgICAgICAgICAgICAgICAgICBwLmRpc3QgPSBucm93KGZpbHRlcihjbGFzc2VzLCBncm91cCA9PSAxIHwgZ3JvdXAgPT0gNCB8IGdyb3VwID09IDgpKS8obnNhbXAuZipuc2FtcC54KSkpCiAgIyB4MgogICMgU2FtcGxlIHgyCiAgaW5mcmFtZSA9IGRhdGEuZnJhbWUoeDIgPSB4LCB4MSA9IHJ1bmlmKG4gPSBuc2FtcC54LCBtaW4gPSAwLCBtYXggPSA1KSkKICBjbGFzc2VzID0gYygpCiAgZm9yKG4gaW4gMTpucm93KGluZnJhbWUpKXsKICAgICMgRXN0aW1hdGUgZGlzdHJpYnV0aW9uIGZvciBmMSwgZjIKICAgIGYxLmVzdCA9IHByZWRpY3Qob2JqZWN0ID0gZjEubW9kLCBuZXdkYXRhID0gaW5mcmFtZSwgdHlwZSA9ICdVSycpCiAgICBmMi5lc3QgPSBwcmVkaWN0KG9iamVjdCA9IGYyLm1vZCwgbmV3ZGF0YSA9IGluZnJhbWUsIHR5cGUgPSAnVUsnKQogICAgdGVzdC5mcmFtZSA9IGRhdGEuZnJhbWUoeDEgPSBpbmZyYW1lJHgxLCB4MiA9IGluZnJhbWUkeDIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBmMSA9IHJub3JtKG4gPSBuc2FtcC5mLCBtZWFuID0gZjEuZXN0JG1lYW4sIHNkID0gZjEuZXN0JHNkKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGYyID0gcm5vcm0obiA9IG5zYW1wLmYsIG1lYW4gPSBmMi5lc3QkbWVhbiwgc2QgPSBmMi5lc3Qkc2QpKQogICAgcmVzID0gcHJlZGljdChvYmplY3QgPSBjbHVzdGVyLmFsbCwgbmV3ZGF0YSA9IHRlc3QuZnJhbWUpCiAgICBjbGFzc2VzID0gYyhjbGFzc2VzLCByZXMkY2xhc3NpZmljYXRpb24pCiAgfQogIGNsYXNzZXMgPSBkYXRhLmZyYW1lKGdyb3VwID0gY2xhc3NlcykKICBnbS5tYXJnaW4gPSByYmluZChnbS5tYXJnaW4sIGRhdGEuZnJhbWUoeCA9IHgsIHZhciA9ICd4MicsIAogICAgICAgICAgICAgICAgICAgICAgICAgcC5wYXJlID0gbnJvdyhmaWx0ZXIoY2xhc3NlcywgZ3JvdXAgPT0gNSB8IGdyb3VwID09IDYpKS8obnNhbXAuZipuc2FtcC54KSwgCiAgICAgICAgICAgICAgICAgICAgICAgICBwLm5lYXIgPSBucm93KGZpbHRlcihjbGFzc2VzLCBncm91cCA9PSAyIHwgZ3JvdXAgPT0gMyB8IGdyb3VwID09IDcpKS8obnNhbXAuZipuc2FtcC54KSwgCiAgICAgICAgICAgICAgICAgICAgICAgICBwLmRpc3QgPSBucm93KGZpbHRlcihjbGFzc2VzLCBncm91cCA9PSAxIHwgZ3JvdXAgPT0gNCB8IGdyb3VwID09IDgpKS8obnNhbXAuZipuc2FtcC54KSkpCn0KCndyaXRlLmNzdihnbS5tYXJnaW4sICdNYXJnaW5fR00uY3N2Jywgcm93Lm5hbWVzID0gRikKYGBgCgpgYGB7cn0KZ20ubWFyZ2luID0gcmVhZC5jc3YoJ01hcmdpbl9HTS5jc3YnKQpnbS5tYXJnaW4gPSBkYXRhLmZyYW1lKHggPSByZXAoZ20ubWFyZ2luJHgsIDMpLAogICAgICAgICAgICAgICAgICAgICAgIHZhciA9IHJlcChnbS5tYXJnaW4kdmFyLCAzKSwKICAgICAgICAgICAgICAgICAgICAgICBwcm9iID0gYyhnbS5tYXJnaW4kcC5wYXJlLCBnbS5tYXJnaW4kcC5uZWFyLCBnbS5tYXJnaW4kcC5kaXN0KSwKICAgICAgICAgICAgICAgICAgICAgICB0eXAgPSBjKHJlcCgnUGFyZXRvJywgbnJvdyhnbS5tYXJnaW4pKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXAoJ05lYXItUGFyZXRvJywgbnJvdyhnbS5tYXJnaW4pKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXAoJ1N1Ym9wdGltYWwnLCBucm93KGdtLm1hcmdpbikpKSkKZ2dwbG90KGdtLm1hcmdpbikgKwogIGdlb21fcGF0aChtYXBwaW5nID0gYWVzKHggPSB4LCB5ID0gcHJvYiwgY29sb3IgPSB0eXApKSArCiAgZmFjZXRfd3JhcCh+dmFyLCBucm93ID0gMikgKwogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gJ0RhcmsyJykgKyAKICBsYWJzKHggPSAnSW5wdXQgVmFsdWUnLCB5ID0gJ0NvbmRpdGlvbmFsIFByb2JhYmlsaXR5JywgY29sb3IgPSAnUmVnaW9uJykKCmdtLm1hcmdpbiA9IHJlYWQuY3N2KCdNYXJnaW5fR00uY3N2JykKZ20ubWFyZ2luID0gZGF0YS5mcmFtZSh4ID0gcmVwKGdtLm1hcmdpbiR4KSwKICAgICAgICAgICAgICAgICAgICAgICB2YXIgPSByZXAoZ20ubWFyZ2luJHZhciksCiAgICAgICAgICAgICAgICAgICAgICAgcHJvYiA9IGdtLm1hcmdpbiRwLnBhcmUgKyBnbS5tYXJnaW4kcC5uZWFyLAogICAgICAgICAgICAgICAgICAgICAgIHR5cCA9ICdPcHRpbWFsJykKZ2dwbG90KGdtLm1hcmdpbikgKwogIGdlb21fcGF0aChtYXBwaW5nID0gYWVzKHggPSB4LCB5ID0gcHJvYiwgY29sb3IgPSB0eXApKSArCiAgZmFjZXRfd3JhcCh+dmFyLCBucm93ID0gMikgKwogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gJ0RhcmsyJykgKyAKICBsYWJzKHggPSAnSW5wdXQgVmFsdWUnLCB5ID0gJ0NvbmRpdGlvbmFsIFByb2JhYmlsaXR5JywgY29sb3IgPSAnUmVnaW9uJykKCmBgYAoKVGhlIGVzdGltYXRlZCBwcm9iYWJpbGl0eSBvZiBiZWluZyBvcHRpbWFsIChQYXJldG8gZnJvbnQgZ3JvdXAgb3IgdGhlIG5lYXItUGFyZXRvIGdyb3VwKSBpcyBzaW1pbGFyIHRvIHRoYXQgY2FsY3VsYXRlZCB0aHJvdWdoIHRoZSBvdGhlciBtZXRob2RzIGZvciB0aGUgUGFyZXRvIGRpc3RhbmNlIG9yIHRocmVzaG9sZCBjcml0ZXJpYS4gSG93ZXZlciwgYmVjYXVzZSBvZiB0aGUgbnVtZXJvdXMgdmFyaWFibGVzIHJlcXVpcmVkIHRvIGdlbmVyYXRlIHRoZSBtb2RlbCwgYSBsYXJnZXIgYW5kIG1vcmUgY29tcGxpY2F0ZWQgc2FtcGxpbmcgcHJvY2VkdXJlIGlzIG5lY2Vzc2FyeSBmb3IgYWNjdXJhdGUgZXN0aW1hdGVzLgoKCiMgT3ZlcmFsbDoKKiBUaGUgdW5zdXBlcnZpc2VkIE1MIG1vZGVsIChHYXVzc2lhbiBtaXh0dXJlKSBjYW4gc29ydCBhY2NvcmRpbmcgdG8gcG9pbnRzIHRoYXQgd291bGQgYmUgc2ltaWxhciB0byB0aGUgUGFyZXRvIGZyb250LiBIb3dldmVyLCBpbiBvcmRlciB0byBvYnRhaW4gdGhlIG1vZGVsLCBhbGwgb2YgdGhlIGlucHV0cyBhbmQgb3V0cHV0cyBhcmUgdXNlZCBpbiB0aGUgdHJhaW5pbmcgZGF0YSwgbWVhbmluZyB0byBzb2x2ZSB0aGUgc2luZ2xlLXZhcmlhYmxlIHByb2JhYmlsaXRpZXMsIG9uZSBtdXN0IHBlcmZvcm0gYSB0aW1lLWNvbnN1bWluZyBuZXN0ZWQgc2FtcGxpbmcgcHJvY2VkdXJlLgoqIFRoZSBzdGFuZGFyZCBzdXBlcnZpc2VkIE1MIG1vZGVsIChTdXBwb3J0IFZlY3RvciBNYWNoaW5lcykgYXJlIGZsZXhpYmxlIHRvIHNlbGVjdGlvbiBjcml0ZXJpYSBiZWNhdXNlIHRob3NlIGFyZSBwYXJ0IG9mIHRoZSB1c2VyIGlucHV0IGludG8gZ2VuZXJhdGluZyB0aGUgbW9kZWwuIEl0IGlzIHZlcnkgc2Vuc2l0aXZlIHRvIHRoZSBrZXJuZWwgZnVuY3Rpb24gdGhhdCB3YXMgc2VsZWN0ZWQsIHdpdGggdGhlIHJhZGlhbCBhbmQgcG9seW5vbWlhbCBmb3JtcyB3b3JraW5nIGJlc3QgZm9yIHRoaXMgc3BlY2lmaWMgY2FzZS4gV2hlbiBjb21wYXJlZCB0byB0aGUgYWN0dWFsIGZ1bmN0aW9uLCB0aGVzZSBnb29kLWZpdHRpbmcga2VybmVscyBhcHBlYXJlZCB0byBiZSBvdmVyLWZpdCB0byB0aGUgZGF0YSBhbmQgY2Fubm90IGNhcHR1cmUgdGhlIGZ1bGwgZHluYW1pY3Mgb2YgdGhlIHNwYWNlLiBJbiBwcmFjdGljZSwgc2VwYXJhdGUgdHJhaW5pbmcgYW5kIHRlc3Rpbmcgc2V0cyBzaG91bGQgYmUgdXNlZCwgd2hpY2ggbWF5IGJlIGRpZmZpY3VsdCBmb3IgbW9yZSBjb21wbGV4IG9iamVjdGl2ZSBmdW5jdGlvbnMgZHVlIHRvIHRoZSBuZWVkIGZvciBtb3JlIHNhbXBsaW5nIGFuZCB0aGVyZWZvcmUgbW9yZSBmdW5jdGlvbiBldmFsdWF0aW9ucy4gVGhlIFNWTSBkaWQgbm90IHN1YnN0YW50aWFsbHkgaW1wcm92ZSB3aXRoIGFkYXB0aXZlIHNhbXBsaW5nLCBpbmRpY2F0aW5nIHRoYXQgdGhlIGV4cGVuZGl0dXJlIGZvciBjb2xsZWN0aW5nIHRoZXNlIHNhbXBsZXMgbWF5IG5vdCBiZSBuZWNlc3NhcnkuCiogVGhlIEdQIGZ1enp5IGNsYXNzaWZpZXIgd29ya3MgYmVzdCB3aXRoIGFkYXB0aXZlIHNhbXBsaW5nLCBpbmNyZWFzaW5nIGl0cyBuZWNlc3NhcnkgY29tcHV0YXRpb25hbCBidWRnZXQsIGJ1dCBhY2NvdW50cyBmb3IgdGhlIHVuY2VydGFpbnR5IGluIHRoZSBtb2RlbCBleHBsaWNpdGx5LiBUaGlzIGhlbHBzIHRoZSBhY2N1cmFjeSBvZiB0aGUgc2luZ2xlLXZhcmlhYmxlIHByb2JhYmlsaXRpZXMgY29tcGFyZWQgdG8gdGhlIFNWTS4KKiBNb3JlIGNvbXBsaWNhdGVkIGNvbmRpdGlvbnMsIHN1Y2ggYXMgb25seSBhY2NlcHRpbmcgcG9pbnRzIHRoYXQgcHJpb3JpdGl6ZSBvbmUgb2JqZWN0aXZlIG92ZXIgdGhlIG90aGVyIGJ5IGEgY2VydGFpbiBtYXJnaW4sIGFyZSBkaWZmaWN1bHQgZm9yIGJvdGggc3VwZXJ2aXNlZCBsZWFybmluZyBtZXRob2RzIHRvIGVzdGltYXRlLCBidXQgdGhlIHJlc3VsdHMgZnJvbSB0aGUgU1ZNIGFwcGVhciB0byBiZSB3b3JzZSBjb21wYXJlZCB0byB0aGUgYWN0dWFsIGJvdW5kYXJpZXMuCiogVGhlIFNWTSBtZXRob2RzIGdpdmUgc2luZ2xlLXZhcmlhYmxlIHByb2JhYmlsaXRpZXMgdGhhdCBhcmUgc2ltaWxhciB0byB0aGUgZXhwZWN0ZWQgbWFyZ2luYWwsIGJ1dCBub3QgYXMgY2xvc2UgYXMgdGhlIEdQIG1ldGhvZC4KCg==